mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-03-21 17:44:58 -05:00
176 lines
3.9 KiB
Go
176 lines
3.9 KiB
Go
package nas
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"wwfc/logging"
|
|
|
|
"github.com/logrusorgru/aurora/v3"
|
|
)
|
|
|
|
func downloadStage1(w http.ResponseWriter, stage1Ver int) {
|
|
path := "payload/stage1.bin"
|
|
if stage1Ver != 0 {
|
|
path = "payload/stage1v" + strconv.Itoa(stage1Ver) + ".bin"
|
|
}
|
|
dat, err := os.ReadFile(path)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
payload := append([]byte{0x01, 0x2C}, dat...)
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(payload)))
|
|
w.Write(payload)
|
|
}
|
|
|
|
func handlePayloadRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
|
|
// Example request:
|
|
// GET /payload?g=RMCPD00&s=4e44b095817f8cfb62e6cffd57e9cfd411004a492784039ea4b2b7ca64717c91&h=9fdb6f60
|
|
|
|
u, err := url.Parse(r.URL.String())
|
|
if err != nil {
|
|
logging.Error(moduleName, "Failed to parse URL")
|
|
return
|
|
}
|
|
|
|
query, err := url.ParseQuery(u.RawQuery)
|
|
if err != nil {
|
|
logging.Error(moduleName, "Failed to parse URL query")
|
|
return
|
|
}
|
|
|
|
// Read payload ID (g) from URL
|
|
game := query.Get("g")
|
|
if len(game) != 7 && len(game) != 9 {
|
|
logging.Error(moduleName, "Invalid or missing game ID:", aurora.Cyan(game))
|
|
return
|
|
}
|
|
|
|
if (len(game) == 7 && game[4] != 'D') || (len(game) == 9 && game[4] != 'N') {
|
|
logging.Error(moduleName, "Invalid game ID:", aurora.Cyan(game))
|
|
return
|
|
}
|
|
|
|
for i := 0; i < 4; i++ {
|
|
if (game[i] >= 'A' && game[i] <= 'Z') || (game[i] >= '0' && game[i] <= '9') {
|
|
continue
|
|
}
|
|
|
|
logging.Error(moduleName, "Invalid game ID char:", aurora.Cyan(game))
|
|
return
|
|
}
|
|
|
|
for i := 5; i < len(game); i++ {
|
|
if (game[i] >= '0' && game[i] <= '9') || (game[i] >= 'a' && game[i] <= 'f') {
|
|
continue
|
|
}
|
|
|
|
logging.Error(moduleName, "Invalid game ID version:", aurora.Cyan(game))
|
|
return
|
|
}
|
|
|
|
dat, err := os.ReadFile("payload/binary/payload." + game + ".bin")
|
|
if err != nil {
|
|
logging.Error(moduleName, "Failed to read payload file")
|
|
return
|
|
}
|
|
|
|
salt, ok := query["s"]
|
|
if ok {
|
|
if len(salt[0]) != 64 {
|
|
logging.Error(moduleName, "Invalid salt length:", aurora.BrightCyan(len(salt[0])))
|
|
return
|
|
}
|
|
|
|
_, err := hex.DecodeString(salt[0])
|
|
if err != nil {
|
|
logging.Error(moduleName, "Invalid salt hex string")
|
|
return
|
|
}
|
|
|
|
saltHashTest, ok := query["h"]
|
|
if !ok {
|
|
logging.Error(moduleName, "Request is missing salt hash")
|
|
return
|
|
}
|
|
|
|
if len(saltHashTest[0]) != 8 {
|
|
logging.Error(moduleName, "Invalid salt hash length:", aurora.BrightCyan(len(saltHashTest[0])))
|
|
return
|
|
}
|
|
|
|
saltHashTestData, err := hex.DecodeString(saltHashTest[0])
|
|
if err != nil {
|
|
logging.Error(moduleName, "Invalid salt hash hex string")
|
|
return
|
|
}
|
|
|
|
// Generate the salt hash
|
|
saltHashData := "payload?g=" + query["g"][0] + "&s=" + query["s"][0]
|
|
|
|
hashCtx := sha256.New()
|
|
_, err = hashCtx.Write([]byte(saltHashData))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
saltHash := hashCtx.Sum(nil)
|
|
if !bytes.Equal(saltHashTestData, saltHash[:4]) {
|
|
logging.Error(moduleName, "Salt hash mismatch")
|
|
return
|
|
}
|
|
|
|
dat = append(append(dat[:0x110], saltHash...), dat[0x130:]...)
|
|
}
|
|
|
|
// TODO: Cache the request for a zero salt
|
|
|
|
rsaData, err := os.ReadFile("payload/private-key.pem")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
rsaBlock, _ := pem.Decode(rsaData)
|
|
parsedKey, err := x509.ParsePKCS8PrivateKey(rsaBlock.Bytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
rsaKey, ok := parsedKey.(*rsa.PrivateKey)
|
|
if !ok {
|
|
panic("Unexpected key type")
|
|
}
|
|
|
|
// Hash our data then sign
|
|
hash := sha256.New()
|
|
_, err = hash.Write(dat[0x110:])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
contentsHashSum := hash.Sum(nil)
|
|
|
|
reader := rand.Reader
|
|
signature, err := rsa.SignPKCS1v15(reader, rsaKey, crypto.SHA256, contentsHashSum)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
dat = append(append(dat[:0x10], signature...), dat[0x110:]...)
|
|
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(dat)))
|
|
w.Write(dat)
|
|
}
|