RACE: Create a foundation for the 'RACE' server

This commit is contained in:
MikeIsAStar 2024-07-10 23:44:30 -04:00
parent ed9aba05f5
commit 1665977524
5 changed files with 220 additions and 2 deletions

110
common/mario_kart_wii.go Normal file
View File

@ -0,0 +1,110 @@
package common
type MarioKartWiiRegionID uint
type MarioKartWiiCourseID uint
const MarioKartWiiGameSpyGameID uint = 1687
const (
Worldwide = iota // 0
Japan = iota // 1
UnitedStates = iota // 2
Europe = iota // 3
Australia = iota // 4
Taiwan = iota // 5
Korea = iota // 6
China = iota // 7
)
const (
MarioCircuit = iota // 0x00
MooMooMeadows = iota // 0x01
MushroomGorge = iota // 0x02
GrumbleVolcano = iota // 0x03
ToadsFactory = iota // 0x04
CoconutMall = iota // 0x05
DKSummit = iota // 0x06
WarioGoldMine = iota // 0x07
LuigiCircuit = iota // 0x08
DaisyCircuit = iota // 0x09
MoonviewHighway = iota // 0x0A
MapleTreeway = iota // 0x0B
BowsersCastle = iota // 0x0C
RainbowRoad = iota // 0x0D
DryDryRuins = iota // 0x0E
KoopaCape = iota // 0x0F
GCNPeachBeach = iota // 0x10
GCNMarioCircuit = iota // 0x11
GCNWaluigiStadium = iota // 0x12
GCNDKMountain = iota // 0x13
DSYoshiFalls = iota // 0x14
DSDesertHills = iota // 0x15
DSPeachGardens = iota // 0x16
DSDelfinoSquare = iota // 0x17
SNESMarioCircuit3 = iota // 0x18
SNESGhostValley2 = iota // 0x19
N64MarioRaceway = iota // 0x1A
N64SherbetLand = iota // 0x1B
N64BowsersCastle = iota // 0x1C
N64DKsJungleParkway = iota // 0x1D
GBABowserCastle3 = iota // 0x1E
GBAShyGuyBeach = iota // 0x1F
)
func (regionId MarioKartWiiRegionID) IsValid() bool {
return regionId >= Worldwide && regionId <= China
}
func (courseId MarioKartWiiCourseID) IsValid() bool {
return courseId >= MarioCircuit && courseId <= GBAShyGuyBeach
}
func (regionId MarioKartWiiRegionID) ToString() string {
return [...]string{
"Worldwide",
"Japan",
"United States",
"Europe",
"Australia",
"Taiwan",
"Korea",
"China",
}[regionId]
}
func (courseId MarioKartWiiCourseID) ToString() string {
return [...]string{
"Mario Circuit",
"Moo Moo Meadows",
"Mushroom Gorge",
"Grumble Volcano",
"Toad's Factory",
"Coconut Mall",
"DK Summit",
"Wario's Gold Mine",
"Luigi Circuit",
"Daisy Circuit",
"Moonview Highway",
"Maple Treeway",
"Bowser's Castle",
"Rainbow Road",
"Dry Dry Ruins",
"Koopa Cape",
"GCN Peach Beach",
"GCN Mario Circuit",
"GCN Waluigi Stadium",
"GCN DK Mountain",
"DS Yoshi Falls",
"DS Desert Hills",
"DS Peach Gardens",
"DS Delfino Square",
"SNES Mario Circuit 3",
"SNES Ghost Valley 2",
"N64 Mario Raceway",
"N64 Sherbet Land",
"N64 Bowser's Castle",
"N64 DK's Jungle Parkway",
"GBA Bowser Castle 3",
"GBA Shy Guy Beach",
}[courseId]
}

View File

@ -22,6 +22,7 @@ import (
"wwfc/nas"
"wwfc/natneg"
"wwfc/qr2"
"wwfc/race"
"wwfc/sake"
"wwfc/serverbrowser"
@ -99,7 +100,7 @@ func backendMain(noSignal, noReload bool) {
}
wg := &sync.WaitGroup{}
actions := []func(bool){nas.StartServer, gpcm.StartServer, qr2.StartServer, gpsp.StartServer, serverbrowser.StartServer, sake.StartServer, natneg.StartServer, api.StartServer, gamestats.StartServer}
actions := []func(bool){nas.StartServer, gpcm.StartServer, qr2.StartServer, gpsp.StartServer, serverbrowser.StartServer, race.StartServer, sake.StartServer, natneg.StartServer, api.StartServer, gamestats.StartServer}
wg.Add(len(actions))
for _, action := range actions {
go func(ac func(bool)) {
@ -215,7 +216,7 @@ func (r *RPCPacket) Shutdown(stateUuid string, _ *struct{}) error {
}
wg := &sync.WaitGroup{}
actions := []func(){nas.Shutdown, gpcm.Shutdown, qr2.Shutdown, gpsp.Shutdown, serverbrowser.Shutdown, sake.Shutdown, natneg.Shutdown, api.Shutdown, gamestats.Shutdown}
actions := []func(){nas.Shutdown, gpcm.Shutdown, qr2.Shutdown, gpsp.Shutdown, serverbrowser.Shutdown, race.Shutdown, sake.Shutdown, natneg.Shutdown, api.Shutdown, gamestats.Shutdown}
wg.Add(len(actions))
for _, action := range actions {
go func(ac func()) {

View File

@ -13,6 +13,7 @@ import (
"wwfc/gamestats"
"wwfc/logging"
"wwfc/nhttp"
"wwfc/race"
"wwfc/sake"
"github.com/logrusorgru/aurora/v3"
@ -71,6 +72,7 @@ func Shutdown() {
}
}
var regexRaceHost = regexp.MustCompile(`^([a-z\-]+\.)?race\.gs\.`)
var regexSakeHost = regexp.MustCompile(`^([a-z\-]+\.)?sake\.gs\.`)
var regexGamestatsHost = regexp.MustCompile(`^([a-z\-]+\.)?gamestats2?\.gs\.`)
var regexStage1URL = regexp.MustCompile(`^/w([0-9])$`)
@ -90,6 +92,13 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
return
}
// Check for *.race.gs.* or race.gs.*
if regexRaceHost.MatchString(r.Host) {
// Redirect to the race server
race.HandleRequest(w, r)
return
}
moduleName := "NAS:" + r.RemoteAddr
// Handle conntest server

25
race/main.go Normal file
View File

@ -0,0 +1,25 @@
package race
import (
"net/http"
"path"
"wwfc/logging"
"github.com/logrusorgru/aurora/v3"
)
func StartServer(reload bool) {
}
func Shutdown() {
}
func HandleRequest(responseWriter http.ResponseWriter, request *http.Request) {
logging.Info("RACE", aurora.Yellow(request.Method), aurora.Cyan(request.URL), "via", aurora.Cyan(request.Host), "from", aurora.BrightCyan(request.RemoteAddr))
switch path.Base(request.URL.Path) {
case "NintendoRacingService.asmx":
moduleName := "RACE:RacingService:" + request.RemoteAddr
handleNintendoRacingServiceRequest(moduleName, responseWriter, request)
}
}

View File

@ -0,0 +1,73 @@
package race
import (
"encoding/xml"
"io"
"net/http"
"wwfc/common"
"wwfc/logging"
"github.com/logrusorgru/aurora/v3"
)
type rankingsRequestEnvelope struct {
XMLName xml.Name
Body rankingsRequestBody
}
type rankingsRequestBody struct {
XMLName xml.Name
Data rankingsRequestData `xml:",any"`
}
type rankingsRequestData struct {
XMLName xml.Name
GameId uint `xml:"gameid"`
RegionId common.MarioKartWiiRegionID `xml:"regionid"`
CourseId common.MarioKartWiiCourseID `xml:"courseid"`
}
func handleNintendoRacingServiceRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
soapActionHeader := request.Header.Get("SOAPAction")
if soapActionHeader == "" {
logging.Error(moduleName, "No SOAPAction header")
return
}
requestBody, err := io.ReadAll(request.Body)
if err != nil {
panic(err)
}
soapMessage := rankingsRequestEnvelope{}
err = xml.Unmarshal(requestBody, &soapMessage)
if err != nil {
logging.Error(moduleName, "Malformed XML")
return
}
soapMessageData := soapMessage.Body.Data
gameId := soapMessageData.GameId
if gameId != common.MarioKartWiiGameSpyGameID {
logging.Error(moduleName, "Wrong GameSpy game id")
return
}
soapAction := soapMessageData.XMLName.Local
switch soapAction {
case "GetTopTenRankings":
regionId := soapMessageData.RegionId
courseId := soapMessageData.CourseId
if !regionId.IsValid() {
logging.Error(moduleName, "Invalid region id")
return
}
if !courseId.IsValid() {
logging.Error(moduleName, "Invalid course id")
return
}
logging.Info(moduleName, "Received a request for the Top 10 of", aurora.BrightCyan(courseId.ToString()))
}
}