feat(sdkserver): add HoYoverse OS passport login routes and universal fallback
This commit is contained in:
1 parent
30bee81fe9
commit
c008243f11
2 files changed
+321
-46
No files matched your search
@@ -139,7 +139,7 @@ func TestEquipmentPickerAuxiliaryRequestsHaveTypedReplies(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if recommendation.EquipAvatar != avatarID || recommendation.BigDataRecommendType != goproto.BigDataRecommendType_BIG_DATA_RECOMMEND_TYPE_AVATAR_RELIC {
|
||||
t.Fatalf("recommendation response lost request context: %+v", recommendation)
|
||||
t.Fatalf("recommendation response lost request context: %+v", &recommendation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+320
-45
@@ -3,15 +3,18 @@
|
||||
package sdkserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gac-hkrpg-go/internal/common"
|
||||
|
||||
google "google.golang.org/protobuf/proto"
|
||||
|
||||
goproto "gac-hkrpg-go/protocol/proto"
|
||||
@@ -213,31 +216,46 @@ func queryGateway(state *appState) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
type accountPayload struct {
|
||||
Data struct {
|
||||
Account map[string]any `json:"account"`
|
||||
} `json:"data"`
|
||||
Message string `json:"message"`
|
||||
Retcode int `json:"retcode"`
|
||||
type authRequestPayload struct {
|
||||
Account string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
UID string `json:"uid"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (a *appState) standardAccount() map[string]any {
|
||||
func parseAuthPayload(r *http.Request) authRequestPayload {
|
||||
var payload authRequestPayload
|
||||
if r.Body != nil {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err == nil && len(body) > 0 {
|
||||
_ = json.Unmarshal(body, &payload)
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func (a *appState) standardAccount(accountName string) map[string]any {
|
||||
if accountName == "" {
|
||||
accountName = a.runtime.AccountName
|
||||
}
|
||||
return map[string]any{
|
||||
"name": a.runtime.AccountName,
|
||||
"account_name": a.runtime.AccountName,
|
||||
"name": accountName,
|
||||
"account_name": accountName,
|
||||
"area_code": "**",
|
||||
"email": a.runtime.AccountName,
|
||||
"country": "RU",
|
||||
"email": accountName,
|
||||
"country": "US",
|
||||
"is_email_verify": "1",
|
||||
"token": "mostsecuretokenever",
|
||||
"uid": fmt.Sprint(a.runtime.PlayerUID),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *appState) loginReply(w http.ResponseWriter) {
|
||||
func (a *appState) loginReply(w http.ResponseWriter, r *http.Request) {
|
||||
req := parseAuthPayload(r)
|
||||
writeJSON(w, map[string]any{
|
||||
"data": map[string]any{
|
||||
"account": a.standardAccount(),
|
||||
"account": a.standardAccount(req.Account),
|
||||
"device_grant_required": false,
|
||||
"reactivate_required": false,
|
||||
"realperson_required": false,
|
||||
@@ -275,20 +293,54 @@ func riskyCheckReply(w http.ResponseWriter) {
|
||||
})
|
||||
}
|
||||
|
||||
func (a *appState) apnLoginReply(w http.ResponseWriter) {
|
||||
func (a *appState) apnLoginReply(w http.ResponseWriter, r *http.Request) {
|
||||
req := parseAuthPayload(r)
|
||||
accName := req.Account
|
||||
if accName == "" {
|
||||
accName = a.runtime.AccountName
|
||||
}
|
||||
uidStr := fmt.Sprint(a.runtime.PlayerUID)
|
||||
|
||||
writeJSON(w, map[string]any{
|
||||
"data": map[string]any{
|
||||
"token": map[string]any{"token": "mostsecuretokenever", "token_type": 1},
|
||||
"bind_email_action_ticket": "",
|
||||
"reactivate_action_token": "",
|
||||
"token": map[string]any{
|
||||
"token": "mostsecuretokenever",
|
||||
"token_type": 1,
|
||||
},
|
||||
"tokens": []any{
|
||||
map[string]any{
|
||||
"token": "mostsecuretokenever",
|
||||
"token_type": 1,
|
||||
},
|
||||
},
|
||||
"user_info": map[string]any{
|
||||
"name": a.runtime.AccountName,
|
||||
"account_name": a.runtime.AccountName,
|
||||
"aid": fmt.Sprint(a.runtime.PlayerUID),
|
||||
"mid": fmt.Sprint(a.runtime.PlayerUID),
|
||||
"is_email_verify": 1,
|
||||
"area_code": "**",
|
||||
"country": "US",
|
||||
"is_adult": 1,
|
||||
"email": a.runtime.AccountName,
|
||||
"name": accName,
|
||||
"account_name": accName,
|
||||
"aid": uidStr,
|
||||
"mid": uidStr,
|
||||
"is_email_verify": 1,
|
||||
"area_code": "**",
|
||||
"country": "US",
|
||||
"is_adult": 1,
|
||||
"email": accName,
|
||||
"mobile": "",
|
||||
"safe_area_code": "",
|
||||
"safe_mobile": "",
|
||||
"realname": "",
|
||||
"identity_code": "",
|
||||
"rebind_area_code": "",
|
||||
"rebind_mobile": "",
|
||||
"rebind_mobile_time": "1",
|
||||
"links": []any{},
|
||||
"password_time": "1",
|
||||
"unmasked_email": accName,
|
||||
"unmasked_email_type": 1,
|
||||
},
|
||||
"ext_user_info": map[string]any{
|
||||
"guardian_email": "",
|
||||
"birth": "0",
|
||||
},
|
||||
},
|
||||
"message": "OK",
|
||||
@@ -296,20 +348,57 @@ func (a *appState) apnLoginReply(w http.ResponseWriter) {
|
||||
})
|
||||
}
|
||||
|
||||
func (a *appState) apnVerifyReply(w http.ResponseWriter) {
|
||||
func (a *appState) apnVerifyReply(w http.ResponseWriter, r *http.Request) {
|
||||
req := parseAuthPayload(r)
|
||||
accName := req.Account
|
||||
if accName == "" {
|
||||
accName = a.runtime.AccountName
|
||||
}
|
||||
uidStr := fmt.Sprint(a.runtime.PlayerUID)
|
||||
if req.UID != "" {
|
||||
uidStr = req.UID
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]any{
|
||||
"data": map[string]any{
|
||||
"tokens": []any{map[string]any{"token": "mostsecuretokenever", "token_type": 1}},
|
||||
"bind_email_action_ticket": "",
|
||||
"reactivate_action_token": "",
|
||||
"token": map[string]any{
|
||||
"token": "mostsecuretokenever",
|
||||
"token_type": 1,
|
||||
},
|
||||
"tokens": []any{
|
||||
map[string]any{
|
||||
"token": "mostsecuretokenever",
|
||||
"token_type": 1,
|
||||
},
|
||||
},
|
||||
"user_info": map[string]any{
|
||||
"name": a.runtime.AccountName,
|
||||
"account_name": a.runtime.AccountName,
|
||||
"aid": fmt.Sprint(a.runtime.PlayerUID),
|
||||
"mid": fmt.Sprint(a.runtime.PlayerUID),
|
||||
"is_email_verify": 1,
|
||||
"area_code": "**",
|
||||
"country": "US",
|
||||
"is_adult": 1,
|
||||
"email": a.runtime.AccountName,
|
||||
"name": accName,
|
||||
"account_name": accName,
|
||||
"aid": uidStr,
|
||||
"mid": uidStr,
|
||||
"is_email_verify": 1,
|
||||
"area_code": "**",
|
||||
"country": "US",
|
||||
"is_adult": 1,
|
||||
"email": accName,
|
||||
"mobile": "",
|
||||
"safe_area_code": "",
|
||||
"safe_mobile": "",
|
||||
"realname": "",
|
||||
"identity_code": "",
|
||||
"rebind_area_code": "",
|
||||
"rebind_mobile": "",
|
||||
"rebind_mobile_time": "1",
|
||||
"links": []any{},
|
||||
"password_time": "1",
|
||||
"unmasked_email": accName,
|
||||
"unmasked_email_type": 1,
|
||||
},
|
||||
"ext_user_info": map[string]any{
|
||||
"guardian_email": "",
|
||||
"birth": "0",
|
||||
},
|
||||
},
|
||||
"message": "OK",
|
||||
@@ -317,6 +406,123 @@ func (a *appState) apnVerifyReply(w http.ResponseWriter) {
|
||||
})
|
||||
}
|
||||
|
||||
func shieldLoadConfigReply(w http.ResponseWriter) {
|
||||
writeJSON(w, map[string]any{
|
||||
"retcode": 0,
|
||||
"message": "OK",
|
||||
"data": map[string]any{
|
||||
"id": 24,
|
||||
"game_key": "hkrpg_global",
|
||||
"client": "PC",
|
||||
"identity": "I_IDENTITY",
|
||||
"guest": false,
|
||||
"ignore_versions": "",
|
||||
"scene": "S_NORMAL",
|
||||
"name": "Honkai: Star Rail",
|
||||
"disable_regist": true,
|
||||
"enable_email_captcha": false,
|
||||
"thirdparty": []string{"fb", "tw", "gl", "ap"},
|
||||
"disable_mmt": false,
|
||||
"server_guest": false,
|
||||
"thirdparty_ignore": map[string]any{},
|
||||
"enable_ps_bind_account": false,
|
||||
"thirdparty_login_configs": map[string]any{
|
||||
"tw": map[string]any{"token_type": "TK_GAME_TOKEN", "game_token_expires_in": 2592000},
|
||||
"ap": map[string]any{"token_type": "TK_GAME_TOKEN", "game_token_expires_in": 2592000},
|
||||
"fb": map[string]any{"token_type": "TK_GAME_TOKEN", "game_token_expires_in": 2592000},
|
||||
"gl": map[string]any{"token_type": "TK_GAME_TOKEN", "game_token_expires_in": 2592000},
|
||||
},
|
||||
"initialize_firebase": false,
|
||||
"bbs_auth_login": false,
|
||||
"bbs_auth_login_ignore": []any{},
|
||||
"fetch_instance_id": false,
|
||||
"enable_flash_login": false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func comboGetConfigReply(w http.ResponseWriter) {
|
||||
writeJSON(w, map[string]any{
|
||||
"retcode": 0,
|
||||
"message": "OK",
|
||||
"data": map[string]any{
|
||||
"protocol": true,
|
||||
"qr_enabled": false,
|
||||
"log_level": "INFO",
|
||||
"announce_url": "",
|
||||
"push_alias_type": 0,
|
||||
"disable_ysdk_guard": true,
|
||||
"enable_announce_pic_popup": false,
|
||||
"app_name": "Honkai: Star Rail",
|
||||
"qr_enabled_apps": map[string]bool{"bbs": false, "cloud": false},
|
||||
"qr_app_icons": map[string]string{"app": "", "bbs": "", "cloud": ""},
|
||||
"qr_cloud_display_name": "",
|
||||
"enable_user_center": true,
|
||||
"functional_switch_configs": map[string]any{},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func compareProtocolVersionReply(w http.ResponseWriter) {
|
||||
writeJSON(w, map[string]any{
|
||||
"retcode": 0,
|
||||
"message": "OK",
|
||||
"data": map[string]any{
|
||||
"modified": false,
|
||||
"protocol": nil,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func agreementInfosReply(w http.ResponseWriter) {
|
||||
writeJSON(w, map[string]any{
|
||||
"retcode": 0,
|
||||
"message": "OK",
|
||||
"data": map[string]any{
|
||||
"marketing_agreements": []any{},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func comboConfigReply(w http.ResponseWriter) {
|
||||
writeJSON(w, map[string]any{
|
||||
"retcode": 0,
|
||||
"message": "OK",
|
||||
"data": map[string]any{
|
||||
"vals": map[string]any{
|
||||
"kibana_pc_config": `{"enable":0,"level":"Info","modules":["download"]}`,
|
||||
"network_report_config": `{"enable":0,"status_codes":[206],"url_paths":["dataUpload","red_dot"]}`,
|
||||
"list_price_tierv2_enable": "false",
|
||||
"enable_web_dpi": "true",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func deviceFpReply(w http.ResponseWriter) {
|
||||
writeJSON(w, map[string]any{
|
||||
"code": 200,
|
||||
"device_fp": "20261030233000",
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func announcementReply(w http.ResponseWriter) {
|
||||
writeJSON(w, map[string]any{
|
||||
"retcode": 0,
|
||||
"message": "OK",
|
||||
"data": map[string]any{
|
||||
"alert": false,
|
||||
"alert_id": 0,
|
||||
"remind": false,
|
||||
"extra_remind": false,
|
||||
"total": 0,
|
||||
"list": []any{},
|
||||
"infos": []any{},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func notFound(w http.ResponseWriter, _ *http.Request) {
|
||||
writeJSON(w, map[string]any{"message": "OK", "retcode": 0})
|
||||
}
|
||||
@@ -358,6 +564,17 @@ func srToolsSave(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, map[string]any{"message": "OK", "status": 200})
|
||||
}
|
||||
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
query := ""
|
||||
if r.URL.RawQuery != "" {
|
||||
query = "?" + r.URL.RawQuery
|
||||
}
|
||||
Log(fmt.Sprintf("[%s] %s%s", r.Method, r.URL.Path, query))
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Listen starts the HTTP sdkserver.
|
||||
func Listen() error {
|
||||
runtime, err := common.LoadRuntimeConfig()
|
||||
@@ -370,26 +587,84 @@ func Listen() error {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/query_dispatch", func(w http.ResponseWriter, r *http.Request) { queryDispatch(w, r, runtime) })
|
||||
mux.HandleFunc("/query_gateway", queryGateway(state))
|
||||
mux.HandleFunc("/{product_name}/mdk/shield/api/login", func(w http.ResponseWriter, r *http.Request) { state.loginReply(w) })
|
||||
mux.HandleFunc("/{product_name}/mdk/shield/api/verify", func(w http.ResponseWriter, r *http.Request) { state.loginReply(w) })
|
||||
mux.HandleFunc("/{product_name}/combo/granter/login/v2/login", func(w http.ResponseWriter, r *http.Request) { state.granterLoginReply(w) })
|
||||
mux.HandleFunc("/account/risky/api/check", func(w http.ResponseWriter, r *http.Request) { riskyCheckReply(w) })
|
||||
mux.HandleFunc("/account/ma-cn-passport/app/loginByPassword", func(w http.ResponseWriter, r *http.Request) { state.apnLoginReply(w) })
|
||||
mux.HandleFunc("/account/ma-cn-session/app/verify", func(w http.ResponseWriter, r *http.Request) { state.apnVerifyReply(w) })
|
||||
|
||||
// HoYoverse / Star Rail Global & CN Authentication Routes
|
||||
mux.HandleFunc("/{product_name}/account/ma-passport/api/appLoginByPassword", func(w http.ResponseWriter, r *http.Request) { state.apnLoginReply(w, r) })
|
||||
mux.HandleFunc("/account/ma-passport/api/appLoginByPassword", func(w http.ResponseWriter, r *http.Request) { state.apnLoginReply(w, r) })
|
||||
mux.HandleFunc("/account/ma-cn-passport/app/loginByPassword", func(w http.ResponseWriter, r *http.Request) { state.apnLoginReply(w, r) })
|
||||
|
||||
mux.HandleFunc("/{product_name}/account/ma-passport/token/verify", func(w http.ResponseWriter, r *http.Request) { state.apnVerifyReply(w, r) })
|
||||
mux.HandleFunc("/account/ma-passport/token/verify", func(w http.ResponseWriter, r *http.Request) { state.apnVerifyReply(w, r) })
|
||||
mux.HandleFunc("/account/ma-cn-session/app/verify", func(w http.ResponseWriter, r *http.Request) { state.apnVerifyReply(w, r) })
|
||||
|
||||
mux.HandleFunc("/{product_name}/mdk/shield/api/login", func(w http.ResponseWriter, r *http.Request) { state.loginReply(w, r) })
|
||||
mux.HandleFunc("/{product_name}/mdk/shield/api/verify", func(w http.ResponseWriter, r *http.Request) { state.loginReply(w, r) })
|
||||
mux.HandleFunc("/{product_name}/mdk/shield/api/loadConfig", func(w http.ResponseWriter, _ *http.Request) { shieldLoadConfigReply(w) })
|
||||
|
||||
mux.HandleFunc("/{product_name}/combo/granter/api/getConfig", func(w http.ResponseWriter, _ *http.Request) { comboGetConfigReply(w) })
|
||||
mux.HandleFunc("/{product_name}/combo/granter/api/compareProtocolVersion", func(w http.ResponseWriter, _ *http.Request) { compareProtocolVersionReply(w) })
|
||||
mux.HandleFunc("/{product_name}/combo/granter/login/v2/login", func(w http.ResponseWriter, _ *http.Request) { state.granterLoginReply(w) })
|
||||
mux.HandleFunc("/{product_name}/mdk/agreement/api/getAgreementInfos", func(w http.ResponseWriter, _ *http.Request) { agreementInfosReply(w) })
|
||||
|
||||
mux.HandleFunc("/combo/box/api/config/sdk/combo", func(w http.ResponseWriter, _ *http.Request) { comboConfigReply(w) })
|
||||
mux.HandleFunc("/combo/box/api/config/sw/precache", func(w http.ResponseWriter, _ *http.Request) { comboConfigReply(w) })
|
||||
mux.HandleFunc("/device-fp/api/getFp", func(w http.ResponseWriter, _ *http.Request) { deviceFpReply(w) })
|
||||
mux.HandleFunc("/device-fp/api/getExtList", func(w http.ResponseWriter, _ *http.Request) { deviceFpReply(w) })
|
||||
|
||||
mux.HandleFunc("/account/risky/api/check", func(w http.ResponseWriter, _ *http.Request) { riskyCheckReply(w) })
|
||||
mux.HandleFunc("/{product_name}/combo/red_dot/list", func(w http.ResponseWriter, _ *http.Request) { announcementReply(w) })
|
||||
mux.HandleFunc("/common/{product_name}/announcement/api/getAlertAnn", func(w http.ResponseWriter, _ *http.Request) { announcementReply(w) })
|
||||
mux.HandleFunc("/common/{product_name}/announcement/api/getAlertPic", func(w http.ResponseWriter, _ *http.Request) { announcementReply(w) })
|
||||
|
||||
mux.HandleFunc("/srtools", corsWrapper(srToolsSave))
|
||||
registerAPIRoutes(mux)
|
||||
|
||||
// Fallback Router: ensures any unmapped path variations from various client versions are caught safely
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" || r.URL.Path == "/dashboard" {
|
||||
path := r.URL.Path
|
||||
if path == "/" || path == "/dashboard" {
|
||||
apiRoot(w, r)
|
||||
return
|
||||
}
|
||||
notFound(w, r)
|
||||
|
||||
Log(fmt.Sprintf("Fallback matching for path: %s", path))
|
||||
|
||||
switch {
|
||||
case strings.Contains(path, "loginByPassword") || strings.Contains(path, "appLoginByPassword"):
|
||||
state.apnLoginReply(w, r)
|
||||
case strings.Contains(path, "ma-passport/token/verify") || strings.Contains(path, "ma-cn-session/app/verify") || strings.HasSuffix(path, "/token/verify"):
|
||||
state.apnVerifyReply(w, r)
|
||||
case strings.Contains(path, "mdk/shield/api/login"):
|
||||
state.loginReply(w, r)
|
||||
case strings.Contains(path, "mdk/shield/api/verify"):
|
||||
state.loginReply(w, r)
|
||||
case strings.Contains(path, "combo/granter/login/v2/login"):
|
||||
state.granterLoginReply(w)
|
||||
case strings.Contains(path, "mdk/shield/api/loadConfig"):
|
||||
shieldLoadConfigReply(w)
|
||||
case strings.Contains(path, "combo/granter/api/getConfig"):
|
||||
comboGetConfigReply(w)
|
||||
case strings.Contains(path, "combo/granter/api/compareProtocolVersion"):
|
||||
compareProtocolVersionReply(w)
|
||||
case strings.Contains(path, "mdk/agreement/api/getAgreementInfos"):
|
||||
agreementInfosReply(w)
|
||||
case strings.Contains(path, "combo/box/api/config"):
|
||||
comboConfigReply(w)
|
||||
case strings.Contains(path, "risky/api/check"):
|
||||
riskyCheckReply(w)
|
||||
case strings.Contains(path, "device-fp"):
|
||||
deviceFpReply(w)
|
||||
case strings.Contains(path, "announcement") || strings.Contains(path, "red_dot"):
|
||||
announcementReply(w)
|
||||
default:
|
||||
notFound(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
addr := runtime.SDKAddress()
|
||||
Log("sdkserver is listening at " + addr)
|
||||
Log(fmt.Sprintf("REST API Gateway is live at %s/ (for GaC-Srtool)", runtime.PublicSDKURL))
|
||||
return http.ListenAndServe(addr, withLuaOverlay(mux, runtime.LuaOverlay))
|
||||
Log(fmt.Sprintf("REST API Gateway is live at %s/", runtime.PublicSDKURL))
|
||||
return http.ListenAndServe(addr, loggingMiddleware(withLuaOverlay(mux, runtime.LuaOverlay)))
|
||||
}
|
||||
|
||||
func Log(msg string) { fmt.Println("[sdkserver]", msg) }
|
||||
Reference in new issue
Block a user