mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-09-14 11:35:50 -05:00
QR2/API: Provide Mii info in /api/groups request
This commit is contained in:
@@ -5,7 +5,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"wwfc/common"
|
||||
"wwfc/qr2"
|
||||
)
|
||||
|
||||
@@ -24,27 +23,6 @@ func HandleGroups(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
groups := qr2.GetGroups(query["game"], query["id"])
|
||||
|
||||
for _, group := range groups {
|
||||
for i, player := range group.Players {
|
||||
filtered := map[string]string{}
|
||||
|
||||
filtered["count"] = player["+localplayers"]
|
||||
filtered["pid"] = player["dwc_pid"]
|
||||
filtered["name"] = player["+ingamesn"]
|
||||
|
||||
if player["gamename"] == "mariokartwii" {
|
||||
filtered["ev"] = player["ev"]
|
||||
filtered["eb"] = player["eb"]
|
||||
pid, err := strconv.ParseUint(player["dwc_pid"], 10, 32)
|
||||
if err == nil {
|
||||
filtered["fc"] = common.CalcFriendCodeString(uint32(pid), "RMCJ")
|
||||
}
|
||||
}
|
||||
|
||||
group.Players[i] = filtered
|
||||
}
|
||||
}
|
||||
|
||||
var jsonData []byte
|
||||
if len(groups) == 0 {
|
||||
jsonData, _ = json.Marshal([]string{})
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package common
|
||||
|
||||
// CalculateMiiCRC
|
||||
// https://github.com/kiwi515/ogws/blob/ee72b2a329c50b773fcdce5c302e5674e5f2cf11/src/RVLFaceLib/RFL_Database.c#L640
|
||||
func CalculateMiiCRC(data []byte) uint16 {
|
||||
// References:
|
||||
// https://wiibrew.org/wiki/Mii_Data
|
||||
// https://github.com/kiwi515/ogws/tree/master/src/RVLFaceLib
|
||||
|
||||
func RFLCalculateCRC(data []byte) uint16 {
|
||||
crc := uint16(0)
|
||||
|
||||
for _, val := range data {
|
||||
@@ -24,3 +26,22 @@ func CalculateMiiCRC(data []byte) uint16 {
|
||||
|
||||
return crc
|
||||
}
|
||||
|
||||
var officialMiiList = []uint64{
|
||||
0x80000000ECFF82D2,
|
||||
0x80000001ECFF82D2,
|
||||
0x80000002ECFF82D2,
|
||||
0x80000003ECFF82D2,
|
||||
0x80000004ECFF82D2,
|
||||
0x80000005ECFF82D2,
|
||||
}
|
||||
|
||||
func RFLSearchOfficialData(id uint64) (bool, int) {
|
||||
for i, v := range officialMiiList {
|
||||
if v == id {
|
||||
return true, i
|
||||
}
|
||||
}
|
||||
|
||||
return false, -1
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
var letterRunes = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
@@ -36,6 +38,18 @@ func GetString(buf []byte) (string, error) {
|
||||
return string(buf[:nullTerminator]), nil
|
||||
}
|
||||
|
||||
func GetWideString(buf []byte, byteOrder binary.ByteOrder) (string, error) {
|
||||
var utf16String []uint16
|
||||
for i := 0; i < len(buf)/2; i++ {
|
||||
if buf[i*2] == 0 && buf[i*2+1] == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
utf16String = append(utf16String, byteOrder.Uint16(buf[i*2:i*2+2]))
|
||||
}
|
||||
return string(utf16.Decode(utf16String)), nil
|
||||
}
|
||||
|
||||
// IsUppercaseAlphanumeric checks if the given string is composed exclusively of uppercase alphanumeric characters.
|
||||
func IsUppercaseAlphanumeric(str string) bool {
|
||||
strLength := len(str)
|
||||
|
||||
140
qr2/group.go
140
qr2/group.go
@@ -1,6 +1,8 @@
|
||||
package qr2
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -253,7 +255,75 @@ func CheckGPReservationAllowed(senderIP uint64, senderPid uint32, destPid uint32
|
||||
}
|
||||
|
||||
func ProcessUSER(senderPid uint32, senderIP uint64, packet []byte) {
|
||||
// TODO
|
||||
moduleName := "QR2:ProcessUSER/" + strconv.FormatUint(uint64(senderPid), 10)
|
||||
|
||||
mutex.Lock()
|
||||
login := logins[senderPid]
|
||||
if login == nil {
|
||||
mutex.Unlock()
|
||||
logging.Warn(moduleName, "Received USER packet from non-existent profile ID", aurora.Cyan(senderPid))
|
||||
return
|
||||
}
|
||||
|
||||
gpErrorCallback := login.GPErrorCallback
|
||||
|
||||
session := login.Session
|
||||
if session == nil {
|
||||
mutex.Unlock()
|
||||
logging.Warn(moduleName, "Received USER packet from profile ID", aurora.Cyan(senderPid), "but no session exists")
|
||||
return
|
||||
}
|
||||
mutex.Unlock()
|
||||
|
||||
miiGroupCount := binary.BigEndian.Uint16(packet[0x04:0x06])
|
||||
if miiGroupCount != 2 {
|
||||
logging.Error(moduleName, "Received USER packet with unexpected Mii group count", aurora.Cyan(miiGroupCount))
|
||||
// Kick the client
|
||||
gpErrorCallback(senderPid, "malpacket")
|
||||
return
|
||||
}
|
||||
|
||||
miiGroupBitflags := binary.BigEndian.Uint32(packet[0x00:0x04])
|
||||
|
||||
var miiData []string
|
||||
var miiName []string
|
||||
for i := 0; i < int(miiGroupCount); i++ {
|
||||
if miiGroupBitflags&(1<<uint(i)) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
index := 0x08 + i*0x4C
|
||||
if common.RFLCalculateCRC(packet[index:index+0x4C]) != 0x0000 {
|
||||
logging.Error(moduleName, "Received USER packet with invalid Mii data CRC")
|
||||
gpErrorCallback(senderPid, "malpacket")
|
||||
return
|
||||
}
|
||||
|
||||
createId := binary.BigEndian.Uint64(packet[index+0x18 : index+0x20])
|
||||
official, _ := common.RFLSearchOfficialData(createId)
|
||||
if official {
|
||||
miiName = append(miiName, "Player")
|
||||
} else {
|
||||
decodedName, err := common.GetWideString(packet[index+0x2:index+0x2+20], binary.BigEndian)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Failed to parse Mii name:", err)
|
||||
gpErrorCallback(senderPid, "malpacket")
|
||||
return
|
||||
}
|
||||
|
||||
miiName = append(miiName, decodedName)
|
||||
}
|
||||
|
||||
miiData = append(miiData, base64.StdEncoding.EncodeToString(packet[index:index+0x4A]))
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
for i, name := range miiName {
|
||||
session.Data["+mii"+strconv.Itoa(i)] = miiData[i]
|
||||
session.Data["+mii_name"+strconv.Itoa(i)] = name
|
||||
}
|
||||
}
|
||||
|
||||
// findNewServer attempts to find the new server/host in the group when the current server goes down.
|
||||
@@ -292,6 +362,23 @@ func (g *Group) updateMatchType() {
|
||||
g.MatchType = g.Server.Data["dwc_mtype"]
|
||||
}
|
||||
|
||||
type MiiInfo struct {
|
||||
MiiData string `json:"data"`
|
||||
MiiName string `json:"name"`
|
||||
}
|
||||
|
||||
type PlayerInfo struct {
|
||||
Count string `json:"count"`
|
||||
ProfileID string `json:"pid"`
|
||||
InGameName string `json:"name"`
|
||||
|
||||
// Mario Kart Wii-specific fields
|
||||
FriendCode string `json:"fc,omitempty"`
|
||||
VersusELO string `json:"ev,omitempty"`
|
||||
BattleELO string `json:"eb,omitempty"`
|
||||
Mii []MiiInfo `json:"mii,omitempty"`
|
||||
}
|
||||
|
||||
type GroupInfo struct {
|
||||
GroupName string `json:"id"`
|
||||
GameName string `json:"game"`
|
||||
@@ -299,11 +386,11 @@ type GroupInfo struct {
|
||||
Suspend bool `json:"suspend"`
|
||||
ServerIndex string `json:"host,omitempty"`
|
||||
MKWRegion string `json:"rk,omitempty"`
|
||||
Players map[string]map[string]string `json:"players"`
|
||||
PlayersRaw map[string]map[string]string `json:"-"`
|
||||
Players []PlayerInfo `json:"players"`
|
||||
}
|
||||
|
||||
// GetGroups returns an unsorted copy of all online rooms
|
||||
func GetGroups(gameNames []string, groupNames []string) []GroupInfo {
|
||||
func getGroupsRaw(gameNames []string, groupNames []string) []GroupInfo {
|
||||
var groupsCopy []GroupInfo
|
||||
|
||||
mutex.Lock()
|
||||
@@ -325,7 +412,7 @@ func GetGroups(gameNames []string, groupNames []string) []GroupInfo {
|
||||
Suspend: true,
|
||||
ServerIndex: "",
|
||||
MKWRegion: "",
|
||||
Players: map[string]map[string]string{},
|
||||
PlayersRaw: map[string]map[string]string{},
|
||||
}
|
||||
|
||||
if group.MatchType == "0" || group.MatchType == "1" {
|
||||
@@ -356,7 +443,7 @@ func GetGroups(gameNames []string, groupNames []string) []GroupInfo {
|
||||
mapData["+ingamesn"] = ""
|
||||
}
|
||||
|
||||
groupInfo.Players[mapData["+joinindex"]] = mapData
|
||||
groupInfo.PlayersRaw[mapData["+joinindex"]] = mapData
|
||||
|
||||
if mapData["dwc_hoststate"] == "2" && mapData["dwc_suspend"] == "0" {
|
||||
groupInfo.Suspend = false
|
||||
@@ -368,3 +455,44 @@ func GetGroups(gameNames []string, groupNames []string) []GroupInfo {
|
||||
|
||||
return groupsCopy
|
||||
}
|
||||
|
||||
// GetGroups returns an unsorted copy of all online rooms
|
||||
func GetGroups(gameNames []string, groupNames []string) []GroupInfo {
|
||||
groupsCopy := getGroupsRaw(gameNames, groupNames)
|
||||
|
||||
for i, group := range groupsCopy {
|
||||
for _, rawPlayer := range group.PlayersRaw {
|
||||
playerInfo := PlayerInfo{
|
||||
Count: rawPlayer["+localplayers"],
|
||||
ProfileID: rawPlayer["dwc_pid"],
|
||||
InGameName: rawPlayer["+ingamesn"],
|
||||
}
|
||||
|
||||
if rawPlayer["gamename"] == "mariokartwii" {
|
||||
playerInfo.VersusELO = rawPlayer["ev"]
|
||||
playerInfo.BattleELO = rawPlayer["eb"]
|
||||
|
||||
pid, err := strconv.ParseUint(rawPlayer["dwc_pid"], 10, 32)
|
||||
if err == nil {
|
||||
playerInfo.FriendCode = common.CalcFriendCodeString(uint32(pid), "RMCJ")
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 32; i++ {
|
||||
miiData := rawPlayer["+mii"+strconv.Itoa(i)]
|
||||
if miiData == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
playerInfo.Mii = append(playerInfo.Mii, MiiInfo{
|
||||
MiiData: miiData,
|
||||
MiiName: rawPlayer["+mii_name"+strconv.Itoa(i)],
|
||||
})
|
||||
}
|
||||
|
||||
groupsCopy[i].Players = append(groupsCopy[i].Players, playerInfo)
|
||||
}
|
||||
}
|
||||
|
||||
return groupsCopy
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user