mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-08-13 12:25:41 -05:00
More server list and general changes
- Server browser (Matchmaking) now returns a server list (although hardcoded) - GPCM now keeps track of an actual thread-safe session - Some int types were changed to reflect the actual size - Improvements made to logging in many places
This commit is contained in:
@@ -23,10 +23,10 @@ func CalcFriendCode(pid uint32, gameId string) uint64 {
|
||||
}
|
||||
|
||||
func CalcFriendCodeString(pid uint32, gameId string) string {
|
||||
return GetFriendCodeString(CalcFriendCode(pid, gameId))
|
||||
return GetRawFriendCodeString(CalcFriendCode(pid, gameId))
|
||||
}
|
||||
|
||||
func GetFriendCodeString(fc uint64) string {
|
||||
func GetRawFriendCodeString(fc uint64) string {
|
||||
s := fmt.Sprintf("%012d", fc)
|
||||
return s[len(s)-12:len(s)-8] + "-" + s[len(s)-8:len(s)-4] + "-" + s[len(s)-4:]
|
||||
}
|
||||
|
||||
@@ -107,14 +107,14 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, authToken string)
|
||||
// Create the GPCM account
|
||||
user.CreateUser(pool, ctx)
|
||||
|
||||
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(strconv.FormatInt(user.UserId, 10)).String(), aurora.Cyan(user.GsbrCode).String(), "-", aurora.Cyan(strconv.FormatInt(user.ProfileId, 10)).String())
|
||||
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(strconv.FormatInt(user.UserId, 10)).String(), aurora.Cyan(user.GsbrCode).String(), "-", aurora.Cyan(strconv.FormatInt(int64(user.ProfileId), 10)).String())
|
||||
} else {
|
||||
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logging.Notice("DATABASE", "Log in GPCM user:", aurora.Cyan(strconv.FormatInt(user.UserId, 10)).String(), aurora.Cyan(user.GsbrCode).String(), "-", aurora.Cyan(strconv.FormatInt(user.ProfileId, 10)).String())
|
||||
logging.Notice("DATABASE", "Log in GPCM user:", aurora.Cyan(strconv.FormatInt(user.UserId, 10)).String(), aurora.Cyan(user.GsbrCode).String(), "-", aurora.Cyan(strconv.FormatInt(int64(user.ProfileId), 10)).String())
|
||||
}
|
||||
|
||||
return user, true
|
||||
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ProfileId int64
|
||||
ProfileId uint32
|
||||
UserId int64
|
||||
GsbrCode string
|
||||
Password string
|
||||
@@ -53,10 +53,7 @@ func UpdateUser(pool *pgxpool.Pool, ctx context.Context, firstName string, lastN
|
||||
return user
|
||||
}
|
||||
|
||||
func CreateSession(pool *pgxpool.Pool, ctx context.Context, profileId int64, loginTicket string) string {
|
||||
// Delete session first.
|
||||
deleteSession(pool, ctx, profileId)
|
||||
|
||||
func CreateSession(pool *pgxpool.Pool, ctx context.Context, profileId uint32, loginTicket string) string {
|
||||
sessionKey := common.RandomString(8)
|
||||
_, err := pool.Exec(ctx, CreateUserSession, sessionKey, profileId, loginTicket)
|
||||
if err != nil {
|
||||
@@ -66,14 +63,14 @@ func CreateSession(pool *pgxpool.Pool, ctx context.Context, profileId int64, log
|
||||
return sessionKey
|
||||
}
|
||||
|
||||
func deleteSession(pool *pgxpool.Pool, ctx context.Context, profileId int64) {
|
||||
func deleteSession(pool *pgxpool.Pool, ctx context.Context, profileId uint32) {
|
||||
_, err := pool.Exec(ctx, DeleteUserSession, profileId)
|
||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetProfile(pool *pgxpool.Pool, ctx context.Context, profileId int64) User {
|
||||
func GetProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) User {
|
||||
user := User{}
|
||||
row := pool.QueryRow(ctx, GetUser, profileId)
|
||||
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Password, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package gcsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
)
|
||||
|
||||
func generateResponse(gpcmChallenge, nasChallenge, authToken, clientChallenge string) string {
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(nasChallenge))
|
||||
str := hex.EncodeToString(hasher.Sum(nil))
|
||||
str += " "
|
||||
str += authToken
|
||||
str += clientChallenge
|
||||
str += gpcmChallenge
|
||||
str += hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
_hasher := md5.New()
|
||||
_hasher.Write([]byte(str))
|
||||
return hex.EncodeToString(_hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func generateProof(gpcmChallenge, nasChallenge, authToken, clientChallenge string) string {
|
||||
return generateResponse(clientChallenge, nasChallenge, authToken, gpcmChallenge)
|
||||
}
|
||||
|
||||
func login(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand, challenge string) string {
|
||||
// TODO: Validate login token with one in database
|
||||
authToken := command.OtherValues["authtoken"]
|
||||
response := generateResponse(challenge, "0qUekMb4", authToken, command.OtherValues["challenge"])
|
||||
if response != command.OtherValues["response"] {
|
||||
log.Fatalf("i hate my life")
|
||||
}
|
||||
|
||||
proof := generateProof(challenge, "0qUekMb4", command.OtherValues["authtoken"], command.OtherValues["challenge"])
|
||||
|
||||
// Perform the login with the database.
|
||||
// TODO: Check valid result
|
||||
user, _ := database.LoginUserToGPCM(pool, ctx, authToken)
|
||||
loginTicket := strings.Replace(base64.StdEncoding.EncodeToString([]byte(common.RandomString(16))), "=", "_", -1)
|
||||
// TODO: Remove in favour of proper thread safe holding
|
||||
userId = user.UserId
|
||||
// Now initiate the session
|
||||
_ = database.CreateSession(pool, ctx, user.ProfileId, loginTicket)
|
||||
|
||||
return common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "lc",
|
||||
CommandValue: "2",
|
||||
OtherValues: map[string]string{
|
||||
"sesskey": "199714190",
|
||||
"proof": proof,
|
||||
"userid": strconv.FormatInt(user.UserId, 10),
|
||||
"profileid": strconv.FormatInt(user.ProfileId, 10),
|
||||
"uniquenick": user.UniqueNick,
|
||||
"lt": loginTicket,
|
||||
"id": command.OtherValues["id"],
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package gcsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"strconv"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
)
|
||||
|
||||
func getProfile(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) string {
|
||||
strProfileId := command.OtherValues["profileid"]
|
||||
profileId, _ := strconv.ParseInt(strProfileId, 10, 0)
|
||||
|
||||
user := database.GetProfile(pool, ctx, profileId)
|
||||
|
||||
_ = common.RandomHexString(32)
|
||||
return common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "pi",
|
||||
CommandValue: "",
|
||||
OtherValues: map[string]string{
|
||||
"profileid": command.OtherValues["profileid"],
|
||||
"nick": user.UniqueNick,
|
||||
"userid": strconv.FormatInt(user.UserId, 10),
|
||||
"email": user.Email,
|
||||
"sig": "b126556e5ee62d4da9629dfad0f6b2a8",
|
||||
"uniquenick": user.UniqueNick,
|
||||
"firstname": user.FirstName,
|
||||
"lastname": user.LastName,
|
||||
"pid": "11",
|
||||
"lon": "0.000000",
|
||||
"lat": "0.000000",
|
||||
"loc": "",
|
||||
"id": command.OtherValues["id"],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func updateProfile(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) {
|
||||
var firstName string
|
||||
var lastName string
|
||||
if v, ok := command.OtherValues["firstname"]; ok {
|
||||
firstName = v
|
||||
}
|
||||
|
||||
if v, ok := command.OtherValues["lastname"]; ok {
|
||||
lastName = v
|
||||
}
|
||||
|
||||
database.UpdateUser(pool, ctx, firstName, lastName, userId)
|
||||
}
|
||||
|
||||
func createStatus() string {
|
||||
return common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "bm",
|
||||
CommandValue: "100",
|
||||
OtherValues: map[string]string{
|
||||
"f": "5",
|
||||
"msg": "|s|0|ss|Offline",
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -32,24 +32,37 @@ func generateProof(gpcmChallenge, nasChallenge, authToken, clientChallenge strin
|
||||
return generateResponse(clientChallenge, nasChallenge, authToken, gpcmChallenge)
|
||||
}
|
||||
|
||||
func login(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand, challenge string) string {
|
||||
func Login(session *GameSpySession, pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand, challenge string) (string, bool) {
|
||||
if session.LoggedIn {
|
||||
log.Fatalf("Attempt to login twice")
|
||||
}
|
||||
|
||||
// TODO: Validate login token with one in database
|
||||
authToken := command.OtherValues["authtoken"]
|
||||
response := generateResponse(challenge, "0qUekMb4", authToken, command.OtherValues["challenge"])
|
||||
if response != command.OtherValues["response"] {
|
||||
log.Fatalf("i hate my life")
|
||||
// TODO: Return an error
|
||||
log.Fatalf("response mismatch")
|
||||
}
|
||||
|
||||
proof := generateProof(challenge, "0qUekMb4", command.OtherValues["authtoken"], command.OtherValues["challenge"])
|
||||
|
||||
// Perform the login with the database.
|
||||
// TODO: Check valid result
|
||||
user, _ := database.LoginUserToGPCM(pool, ctx, authToken)
|
||||
user, ok := database.LoginUserToGPCM(pool, ctx, authToken)
|
||||
if !ok {
|
||||
// TODO: Return an error
|
||||
log.Fatalf("GPCM login error")
|
||||
}
|
||||
session.User = user
|
||||
|
||||
loginTicket := strings.Replace(base64.StdEncoding.EncodeToString([]byte(common.RandomString(16))), "=", "_", -1)
|
||||
// TODO: Remove in favour of proper thread safe holding
|
||||
userId = user.UserId
|
||||
// Now initiate the session
|
||||
_ = database.CreateSession(pool, ctx, user.ProfileId, loginTicket)
|
||||
_ = database.CreateSession(pool, ctx, session.User.ProfileId, loginTicket)
|
||||
|
||||
session.LoggedIn = true
|
||||
session.ModuleName += ":" + strconv.FormatInt(int64(session.User.ProfileId), 10)
|
||||
session.ModuleName += "/" + common.CalcFriendCodeString(session.User.ProfileId, "RMCJ")
|
||||
|
||||
return common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "lc",
|
||||
@@ -57,11 +70,11 @@ func login(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyComman
|
||||
OtherValues: map[string]string{
|
||||
"sesskey": "199714190",
|
||||
"proof": proof,
|
||||
"userid": strconv.FormatInt(user.UserId, 10),
|
||||
"profileid": strconv.FormatInt(user.ProfileId, 10),
|
||||
"uniquenick": user.UniqueNick,
|
||||
"userid": strconv.FormatInt(session.User.UserId, 10),
|
||||
"profileid": strconv.FormatInt(int64(session.User.ProfileId), 10),
|
||||
"uniquenick": session.User.UniqueNick,
|
||||
"lt": loginTicket,
|
||||
"id": command.OtherValues["id"],
|
||||
},
|
||||
})
|
||||
}), true
|
||||
}
|
||||
|
||||
61
gpcm/main.go
61
gpcm/main.go
@@ -11,13 +11,19 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
type GameSpySession struct {
|
||||
User database.User
|
||||
ModuleName string
|
||||
LoggedIn bool
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
pool *pgxpool.Pool
|
||||
userId int64
|
||||
ctx = context.Background()
|
||||
pool *pgxpool.Pool
|
||||
)
|
||||
|
||||
func StartServer() {
|
||||
@@ -60,6 +66,12 @@ func StartServer() {
|
||||
|
||||
// Handles incoming requests.
|
||||
func handleRequest(conn net.Conn) {
|
||||
session := GameSpySession{
|
||||
User: database.User{},
|
||||
ModuleName: "GPCM",
|
||||
LoggedIn: false,
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
// Set session ID and challenge
|
||||
@@ -67,19 +79,17 @@ func handleRequest(conn net.Conn) {
|
||||
|
||||
err := conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
if err != nil {
|
||||
logging.Notice("GPCM", "Unable to set keepalive:", err.Error())
|
||||
logging.Notice(session.ModuleName, "Unable to set keepalive:", err.Error())
|
||||
}
|
||||
|
||||
err = conn.(*net.TCPConn).SetKeepAlivePeriod(time.Hour * 1000)
|
||||
if err != nil {
|
||||
logging.Notice("GPCM", "Unable to set keepalive:", err.Error())
|
||||
logging.Notice(session.ModuleName, "Unable to set keepalive:", err.Error())
|
||||
}
|
||||
|
||||
conn.Write([]byte(fmt.Sprintf(`\lc\1\challenge\%s\id\1\final\`, challenge)))
|
||||
|
||||
logging.Notice("GPCM", "Connection established from", conn.RemoteAddr().String())
|
||||
|
||||
loggedIn := false
|
||||
logging.Notice(session.ModuleName, "Connection established from", conn.RemoteAddr().String())
|
||||
|
||||
// Here we go into the listening loop
|
||||
for {
|
||||
@@ -88,32 +98,31 @@ func handleRequest(conn net.Conn) {
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
// Client closed connection, terminate.
|
||||
logging.Notice("GPCM", "Client closed connection")
|
||||
logging.Notice(session.ModuleName, "Client closed connection")
|
||||
return
|
||||
}
|
||||
|
||||
logging.Notice(session.ModuleName, "Connection lost")
|
||||
return
|
||||
}
|
||||
|
||||
commands, err := common.ParseGameSpyMessage(string(buffer))
|
||||
if err != nil {
|
||||
logging.Notice("GPCM", "Error parsing message:", err.Error())
|
||||
logging.Notice("GPCM", "Raw data:", string(buffer))
|
||||
logging.Notice(session.ModuleName, "Error parsing message:", err.Error())
|
||||
logging.Notice(session.ModuleName, "Raw data:", string(buffer))
|
||||
return
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
logging.Notice("GPCM", "Command:", aurora.Yellow(command.Command).String())
|
||||
logging.Notice(session.ModuleName, "Command:", aurora.Yellow(command.Command).String())
|
||||
|
||||
if loggedIn == false {
|
||||
if session.LoggedIn == false {
|
||||
if command.Command != "login" {
|
||||
logging.Notice("GPCM", "Attempt to run command before login!!!")
|
||||
logging.Notice(session.ModuleName, "Attempt to run command before login!!!")
|
||||
return
|
||||
}
|
||||
|
||||
payload := login(pool, ctx, command, challenge)
|
||||
if userId != 0 {
|
||||
loggedIn = true
|
||||
}
|
||||
|
||||
payload, _ := Login(&session, pool, ctx, command, challenge)
|
||||
conn.Write([]byte(payload))
|
||||
}
|
||||
}
|
||||
@@ -130,24 +139,24 @@ func handleRequest(conn net.Conn) {
|
||||
return
|
||||
|
||||
case "updatepro":
|
||||
updateProfile(pool, ctx, command)
|
||||
UpdateProfile(&session, pool, ctx, command)
|
||||
break
|
||||
|
||||
case "status":
|
||||
logging.Notice("GPCM", "statstring:", aurora.Cyan(command.OtherValues["statstring"]).String())
|
||||
logging.Notice(session.ModuleName, "statstring:", aurora.Cyan(command.OtherValues["statstring"]).String())
|
||||
if command.OtherValues["locstring"] == "" {
|
||||
logging.Notice("GPCM", "locstring: (empty)")
|
||||
logging.Notice(session.ModuleName, "locstring: (empty)")
|
||||
} else {
|
||||
logging.Notice("GPCM", "locstring:", aurora.Cyan(command.OtherValues["locstring"]).String())
|
||||
logging.Notice(session.ModuleName, "locstring:", aurora.Cyan(command.OtherValues["locstring"]).String())
|
||||
}
|
||||
break
|
||||
|
||||
case "addbuddy":
|
||||
addFriend(pool, ctx, command)
|
||||
AddFriend(&session, pool, ctx, command)
|
||||
break
|
||||
|
||||
case "delbuddy":
|
||||
removeFriend(pool, ctx, command)
|
||||
RemoveFriend(&session, pool, ctx, command)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -159,7 +168,7 @@ func handleRequest(conn net.Conn) {
|
||||
break
|
||||
|
||||
case "getprofile":
|
||||
payload := getProfile(pool, ctx, command)
|
||||
payload := GetProfile(&session, pool, ctx, command)
|
||||
conn.Write([]byte(payload))
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2,20 +2,22 @@ package gpcm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"strconv"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func getProfile(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) string {
|
||||
func GetProfile(session *GameSpySession, pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) string {
|
||||
strProfileId := command.OtherValues["profileid"]
|
||||
profileId, _ := strconv.ParseInt(strProfileId, 10, 0)
|
||||
profileId, err := strconv.ParseInt(strProfileId, 10, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
user := database.GetProfile(pool, ctx, profileId)
|
||||
user := database.GetProfile(pool, ctx, uint32(profileId))
|
||||
|
||||
_ = common.RandomHexString(32)
|
||||
return common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
@@ -39,7 +41,7 @@ func getProfile(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyC
|
||||
})
|
||||
}
|
||||
|
||||
func updateProfile(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) {
|
||||
func UpdateProfile(session *GameSpySession, pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) {
|
||||
var firstName string
|
||||
var lastName string
|
||||
if v, ok := command.OtherValues["firstname"]; ok {
|
||||
@@ -50,24 +52,22 @@ func updateProfile(pool *pgxpool.Pool, ctx context.Context, command common.GameS
|
||||
lastName = v
|
||||
}
|
||||
|
||||
database.UpdateUser(pool, ctx, firstName, lastName, userId)
|
||||
database.UpdateUser(pool, ctx, firstName, lastName, session.User.UserId)
|
||||
}
|
||||
|
||||
func addFriend(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) {
|
||||
profileid := command.OtherValues["newprofileid"]
|
||||
|
||||
profileid_int, err := strconv.ParseUint(profileid, 10, 32)
|
||||
func AddFriend(session *GameSpySession, pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) {
|
||||
strProfileId := command.OtherValues["newprofileid"]
|
||||
profileId, err := strconv.ParseUint(strProfileId, 10, 32)
|
||||
if err != nil {
|
||||
logging.Notice("GPCM", "Error parsing profileid:", err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fc := common.CalcFriendCodeString(uint32(profileid_int), "RMCJ")
|
||||
logging.Notice("GPCM", "Add friend:", aurora.Cyan(profileid).String(), aurora.Cyan(fc).String())
|
||||
fc := common.CalcFriendCodeString(uint32(profileId), "RMCJ")
|
||||
logging.Notice(session.ModuleName, "Add friend:", aurora.Cyan(strProfileId).String(), aurora.Cyan(fc).String())
|
||||
// TODO
|
||||
}
|
||||
|
||||
func removeFriend(pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) {
|
||||
func RemoveFriend(session *GameSpySession, pool *pgxpool.Pool, ctx context.Context, command common.GameSpyCommand) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,18 @@ package master
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
sessionId := binary.BigEndian.Uint32(buffer[1:5])
|
||||
logging.Notice("AVAILABLE", "Received heartbeat from", addr.String())
|
||||
moduleName := "AVAILABLE:" + strconv.FormatInt(int64(sessionId), 10)
|
||||
|
||||
logging.Notice(moduleName, "Received heartbeat from", aurora.Cyan(addr).String())
|
||||
values := strings.Split(string(buffer[5:]), "\u0000")
|
||||
|
||||
payload := map[string]string{}
|
||||
@@ -19,12 +23,31 @@ func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
}
|
||||
|
||||
payload[values[i]] = values[i+1]
|
||||
logging.Notice(moduleName, aurora.Cyan(values[i]).String()+":", aurora.Cyan(values[i+1]).String())
|
||||
}
|
||||
|
||||
publicip, ok := payload["publicip"]
|
||||
if ok && publicip != "0" {
|
||||
if !ok || publicip == "0" {
|
||||
sendChallenge(conn, addr, sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
sendChallenge(conn, addr, sessionId)
|
||||
// TODO: Check if the client is registered
|
||||
|
||||
statechanged, ok := payload["statechanged"]
|
||||
if ok {
|
||||
if statechanged == "1" {
|
||||
// statechanged is 1 and publicip is not 0
|
||||
// TODO: This would be a good place to run the server->client message exploit
|
||||
// for DNS patcher games that require code patches. The status code should be
|
||||
// set to 5 at this point, which is required.
|
||||
logging.Notice(moduleName, "Client server update")
|
||||
// Fall through
|
||||
}
|
||||
|
||||
if statechanged == "2" {
|
||||
logging.Notice(moduleName, "Client server shutdown")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,23 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
ServerList = iota
|
||||
ModuleName = "MATCHMAKING"
|
||||
|
||||
// Requests sent from the client
|
||||
ServerListRequest = 0x00
|
||||
ServerInfoRequest = 0x01
|
||||
SendMessageRequest = 0x02
|
||||
KeepaliveReply = 0x03
|
||||
MapLoopRequest = 0x04
|
||||
PlayerSearchRequest = 0x05
|
||||
|
||||
// Requests sent from the server to the client
|
||||
PushKeysMessage = 0x01
|
||||
PushServerMessage = 0x02
|
||||
KeepaliveMessage = 0x03
|
||||
DeleteServerMessage = 0x04
|
||||
MapLoopMessage = 0x05
|
||||
PlayerSearchMessage = 0x06
|
||||
)
|
||||
|
||||
func StartServer() {
|
||||
@@ -91,10 +106,30 @@ func handleRequest(conn net.Conn) {
|
||||
}
|
||||
|
||||
switch buffer[2] {
|
||||
case ServerList:
|
||||
logging.Notice(ModuleName, "Command:", aurora.Yellow("SERVER_LIST").String())
|
||||
case ServerListRequest:
|
||||
logging.Notice(ModuleName, "Command:", aurora.Yellow("SERVER_LIST_REQUEST").String())
|
||||
handleServerListRequest(conn, buffer)
|
||||
break
|
||||
|
||||
serverList(conn, buffer)
|
||||
case ServerInfoRequest:
|
||||
logging.Notice(ModuleName, "Command:", aurora.Yellow("SERVER_INFO_REQUEST").String())
|
||||
break
|
||||
|
||||
case SendMessageRequest:
|
||||
logging.Notice(ModuleName, "Command:", aurora.Yellow("SEND_MESSAGE_REQUEST").String())
|
||||
// TODO
|
||||
break
|
||||
|
||||
case KeepaliveReply:
|
||||
logging.Notice(ModuleName, "Command:", aurora.Yellow("KEEPALIVE_REPLY").String())
|
||||
break
|
||||
|
||||
case MapLoopRequest:
|
||||
logging.Notice(ModuleName, "Command:", aurora.Yellow("MAPLOOP_REQUEST").String())
|
||||
break
|
||||
|
||||
case PlayerSearchRequest:
|
||||
logging.Notice(ModuleName, "Command:", aurora.Yellow("PLAYER_SEARCH_REQUEST").String())
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
@@ -10,6 +10,55 @@ import (
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
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
|
||||
NoServerListOption = 1 << 1 // 0x02 / 2
|
||||
PushUpdatesOption = 1 << 2 // 0x04 / 4
|
||||
AlternateSourceIPOption = 1 << 3 // 0x08 / 8
|
||||
NoListCacheOption = 1 << 6 // 0x40 / 64
|
||||
LimitResultCountOption = 1 << 7 // 0x80 / 128
|
||||
)
|
||||
|
||||
func FindServers(gueryGame string, filter string) ([]map[string]string, error) {
|
||||
// TODO
|
||||
|
||||
// This is a temporary hardcoded server
|
||||
server := map[string]string{
|
||||
"localip0": "192.168.1.100",
|
||||
"localport": "64174",
|
||||
"natneg": "1",
|
||||
"gamename": "mariokartwii",
|
||||
"publicip": "2130706433", // 127.0.0.1
|
||||
"publicport": "64174",
|
||||
"numplayers": "0",
|
||||
"maxplayers": "11",
|
||||
"dwc_pid": "27",
|
||||
"dwc_mtype": "2",
|
||||
"dwc_mver": "90",
|
||||
"dwc_eval": "1",
|
||||
"dwc_groupid": "100000027",
|
||||
"dwc_hoststate": "2",
|
||||
"dwc_suspend": "1",
|
||||
}
|
||||
|
||||
return []map[string]string{server}, nil
|
||||
}
|
||||
|
||||
func popString(buffer []byte, index int) (string, int) {
|
||||
str := common.GetString(buffer[index:])
|
||||
return str, index + len(str) + 1
|
||||
@@ -23,20 +72,14 @@ func popUint32(buffer []byte, index int) (uint32, int) {
|
||||
return binary.BigEndian.Uint32(buffer[index:]), index + 4
|
||||
}
|
||||
|
||||
func serverList(conn net.Conn, buffer []byte) {
|
||||
const (
|
||||
FlagNoServerList = 1 << 1 // 0x02
|
||||
FlagAlternateSourceIP = 1 << 3 // 0x08
|
||||
FlagLimitResultCount = 1 << 7 // 0x80
|
||||
)
|
||||
|
||||
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)
|
||||
flags, index := popUint32(buffer, index)
|
||||
options, index := popUint32(buffer, index)
|
||||
|
||||
logging.Notice(ModuleName, "queryGame:", aurora.Cyan(queryGame).String(), "- gameName:", aurora.Cyan(gameName).String(), "- filter:", aurora.Cyan(filter).String(), "- fields:", aurora.Cyan(fields).String())
|
||||
|
||||
@@ -59,7 +102,7 @@ func serverList(conn net.Conn, buffer []byte) {
|
||||
fieldList = append(fieldList, field)
|
||||
}
|
||||
|
||||
if flags&FlagNoServerList != 0 || len(fieldList) == 0 {
|
||||
if options&NoServerListOption != 0 || len(fieldList) == 0 {
|
||||
// The client requests its own public IP and game port
|
||||
logging.Notice(ModuleName, "Reply without server list", aurora.Cyan(conn.RemoteAddr()).String())
|
||||
|
||||
@@ -82,15 +125,131 @@ func serverList(conn net.Conn, buffer []byte) {
|
||||
|
||||
output = append(output, byte(len(fieldList)))
|
||||
for _, field := range fieldList {
|
||||
output = append(output, 0x00) // Value?
|
||||
output = append(output, []byte(field)...)
|
||||
output = append(output, 0x00) // String end
|
||||
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
|
||||
|
||||
// TODO: Send server list here
|
||||
servers, err := FindServers(queryGame, filter)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Server with no flags and -1 tells the client the message has ended
|
||||
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.Notice(ModuleName, "Server exists without public IP")
|
||||
continue
|
||||
}
|
||||
|
||||
ip, err := strconv.ParseUint(publicip, 10, 32)
|
||||
if err != nil {
|
||||
logging.Notice(ModuleName, "Server has invalid public IP value")
|
||||
}
|
||||
|
||||
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.Notice(ModuleName, "Server exists without port")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
portValue, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
logging.Notice(ModuleName, "Server has invalid port value")
|
||||
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.Notice(ModuleName, "Server has invalid local IP")
|
||||
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.Notice(ModuleName, "Server has invalid local IP value")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if localport, exists := server["localport"]; exists {
|
||||
portValue, err = strconv.ParseUint(localport, 10, 16)
|
||||
if err != nil {
|
||||
logging.Notice(ModuleName, "Server has invalid local port value")
|
||||
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.Notice(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.Notice(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
|
||||
|
||||
Reference in New Issue
Block a user