wfc-server/gamestats/web.go
2026-04-06 07:07:23 -04:00

143 lines
3.6 KiB
Go

package gamestats
import (
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"net/http"
"net/url"
"strconv"
"strings"
"wwfc/common"
"wwfc/logging"
"github.com/logrusorgru/aurora/v3"
)
func HandleWebRequest(w http.ResponseWriter, r *http.Request) {
logging.Info("GSTATS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
moduleName := "GSTATS:" + r.RemoteAddr
u, err := url.Parse(r.URL.String())
if err != nil {
replyHTTPError(w, http.StatusBadRequest, "400 Bad Request")
return
}
query, err := url.ParseQuery(u.RawQuery)
if err != nil {
replyHTTPError(w, http.StatusBadRequest, "400 Bad Request")
return
}
path := u.Path
path = strings.TrimPrefix(path, "/")
gameName := path
subPath := ""
slashIndex := strings.Index(gameName, "/")
if slashIndex != -1 {
gameName = gameName[:slashIndex]
subPath = path[slashIndex:]
}
game := common.GetGameInfoByName(gameName)
if game == nil {
replyHTTPError(w, http.StatusNotFound, "404 Not Found")
return
}
var response []byte
hash := query.Get("hash")
token := calculateToken(r.URL, r.Host)
if hash == "" {
// No hash, just return token
response = []byte(token)
} else {
// Check hash supplied by client
hasher := sha1.New()
hasher.Write([]byte(game.GameStatsKey))
hasher.Write([]byte(token))
expectedHash := hex.EncodeToString(hasher.Sum(nil))
if hash != expectedHash {
logging.Warn(moduleName, "Invalid hash")
}
switch subPath {
case "/web/client/get2.asp":
response = handleGet2(game, query)
default:
logging.Warn(moduleName, "Unhandled path:", aurora.Cyan(subPath))
}
// Padding to appease DWC
response = append(response, make([]byte, 13)...)
if game.GameStatsVersion > 1 {
// SHA-1 hash GamestatsKey + base64(data) + GameStatsKey
hashData := game.GameStatsKey + base64.URLEncoding.EncodeToString([]byte(response)) + game.GameStatsKey
hasher := sha1.New()
hasher.Write([]byte(hashData))
// Append the hash sum as a hex string
response = append(response, []byte(hex.EncodeToString(hasher.Sum(nil)))...)
}
}
w.Header().Set("Content-type", "text/html")
w.Header().Set("Server", "Microsoft-IIS/6.0")
w.Header().Add("Server", "GSTPRDSTATSWEB2")
w.Header().Set("X-Powered-By", "ASP.NET")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.WriteHeader(200)
_, err = w.Write(response)
if err != nil {
logging.Error("GSTATS", "Error writing response:", err)
}
}
func calculateToken(u *url.URL, host string) string {
newURL := *u
newURL.RawQuery = url.Values{
"pid": {u.Query().Get("pid")},
}.Encode()
hasher := sha256.New()
hasher.Write([]byte(host + newURL.String()))
hasher.Write([]byte(webSalt))
return base64.URLEncoding.EncodeToString(hasher.Sum(nil))[:32]
}
func handleGet2(game *common.GameInfo, query url.Values) []byte {
// TODO
common.MaybeUnused(game, query)
data := binary.LittleEndian.AppendUint32([]byte{}, 1) // RNK_GET
data = binary.LittleEndian.AppendUint32(data, 0) // count
return data
}
func replyHTTPError(w http.ResponseWriter, errorCode int, errorString string) {
response := "<html>\n" +
"<head><title>" + errorString + "</title></head>\n" +
"<body>\n" +
"<center><h1>" + errorString + "</h1></center>\n" +
"<hr><center>" + serverName + "</center>\n" +
"</body>\n" +
"</html>\n"
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.Header().Set("Connection", "close")
w.WriteHeader(errorCode)
_, err := w.Write([]byte(response))
if err != nil {
logging.Error("GSTATS", "Error writing response:", err)
}
}