Files
BlueArchiveToolkit/internal/api/launcher.go
T
nyaKazuha c17904ee1c
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s
fix(repo):统一发布健康与只读质量门禁
2026-09-13 22:09:44 +08:00

210 lines
7.0 KiB
Go

package api
import (
"fmt"
"net/http"
"net/url"
"strings"
)
// LauncherAPIHost is the official JP launcher API host observed from the PC launcher.
const LauncherAPIHost = "api-launcher-jp.yo-star.com"
func (s *Server) handleLauncherBootstrap(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeErrorJSON(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
return
}
sum := s.releaseSummary()
status := http.StatusOK
if !sum.Ready || sum.Snapshot == nil {
status = http.StatusServiceUnavailable
}
writeNoStoreJSON(w, status, s.launcherBootstrapBody(sum))
}
func (s *Server) handleLauncherGameConfig(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeErrorJSON(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
return
}
sum := s.releaseSummary()
if !sum.Ready || sum.Snapshot == nil {
writeLauncherEnvelope(w, http.StatusServiceUnavailable, 503, "release not ready", LauncherGameConfigJSONData{
ResourceBootstrapURL: s.launcherBootstrapURL(),
Scope: "resource_bootstrap_only",
})
return
}
snap := sum.Snapshot
data := LauncherGameConfigData{
GameLatestVersion: defaultString(s.launcherLatestVersion(snap), snap.AppVersion),
GameLatestFilePath: s.launcherLatestFilePath(snap),
GameStartExeName: s.launcherStartExeName(snap),
GameStartParams: s.launcherStartParams(snap),
ResourceBootstrapURL: s.launcherBootstrapURL(),
ServerInfoURL: s.serverInfoURL(),
ClientPatchBaseURL: s.clientPatchBaseURL(),
Scope: "resource_bootstrap_only",
}
if snap.LauncherMetadata != nil && snap.LauncherMetadata.GameLowestVersion != "" {
data.GameLowestVersion = snap.LauncherMetadata.GameLowestVersion
}
writeLauncherEnvelope(w, http.StatusOK, 200, "ok", data)
}
func (s *Server) handleLauncherGameConfigJSON(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeErrorJSON(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
return
}
sum := s.releaseSummary()
if !sum.Ready || sum.Snapshot == nil {
writeLauncherEnvelope(w, http.StatusServiceUnavailable, 503, "release not ready", LauncherGameConfigJSONData{
URL: s.launcherBootstrapURL(),
ResourceBootstrapURL: s.launcherBootstrapURL(),
Scope: "resource_bootstrap_only",
})
return
}
writeLauncherEnvelope(w, http.StatusOK, 200, "ok", LauncherGameConfigJSONData{
URL: s.launcherBootstrapJSONURL(r.URL.Query()),
ResourceBootstrapURL: s.launcherBootstrapURL(),
PackageUpdateManifest: false,
Scope: "resource_bootstrap_only",
})
}
func (s *Server) handleLauncherCdnConfig(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeErrorJSON(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
return
}
writeLauncherEnvelope(w, http.StatusOK, 200, "ok", LauncherCdnConfigData{
PrimaryCDN: strings.TrimRight(s.cfg.PublicBaseURL, "/"),
BackupCDN: strings.TrimRight(s.cfg.PublicBaseURL, "/"),
ResourceBootstrapURL: s.launcherBootstrapURL(),
PackageUpdateManifest: false,
Scope: "resource_bootstrap_only",
})
}
func (s *Server) launcherBootstrapBody(sum ReleaseSummary) LauncherBootstrapResponse {
publicBase := strings.TrimRight(s.cfg.PublicBaseURL, "/")
body := LauncherBootstrapResponse{
Service: "bat-api",
Ready: sum.Ready,
LauncherAPI: LauncherAPIInfo{
Host: LauncherAPIHost,
ObservedResourceRelevantEndpoints: []string{
"/api/launcher/game/config",
"/api/launcher/game/config/json",
"/api/launcher/advanced/game/download/cdn",
},
CompatibilityScope: "resource_bootstrap_only",
},
Policy: LauncherPolicy{
Source: "rust_bat_snapshot",
DownloadsLauncherPackage: false,
EmulatesLoginOrGateway: false,
PackageUpdateManifest: false,
},
Resource: LauncherResource{
Release: sum.Snapshot,
Distribution: sum.Distribution,
ServerInfoURL: s.serverInfoURL(),
ClientPatchBaseURL: s.clientPatchBaseURL(),
},
Endpoints: LauncherEndpointSet{
Bootstrap: publicBase + "/v1/bootstrap",
LauncherBootstrap: publicBase + "/v1/launcher/bootstrap",
ServerInfo: s.serverInfoURL(),
ClientPatchBase: s.clientPatchBaseURL(),
LauncherGameConfig: publicBase + "/" + LauncherAPIHost + "/api/launcher/game/config",
LauncherGameConfigJSON: publicBase + "/" + LauncherAPIHost + "/api/launcher/game/config/json",
LauncherCdnConfig: publicBase + "/" + LauncherAPIHost + "/api/launcher/advanced/game/download/cdn",
},
}
if sum.Snapshot != nil {
body.GameMainConfig = sum.Snapshot.GameMainConfig
body.LauncherMetadata = sum.Snapshot.LauncherMetadata
if rewritten, ok := rewriteAddressablesRoot(sum.Snapshot.AddressablesRoot, publicBase); ok {
body.AddressablesCatalogURLRoot = rewritten
}
}
return body
}
func (s *Server) releaseSummary() ReleaseSummary {
if idx := s.index(); idx != nil {
return idx.Summary()
}
return ReleaseSummary{}
}
func (s *Server) serverInfoURL() string {
return strings.TrimRight(s.cfg.PublicBaseURL, "/") + "/" + ServerInfoHost + "/server-info.json"
}
func (s *Server) clientPatchBaseURL() string {
return strings.TrimRight(s.cfg.PublicBaseURL, "/") + "/" + ClientPatchHost
}
func (s *Server) launcherBootstrapURL() string {
return strings.TrimRight(s.cfg.PublicBaseURL, "/") + "/v1/launcher/bootstrap"
}
func (s *Server) launcherBootstrapJSONURL(query url.Values) string {
base := strings.TrimRight(s.cfg.PublicBaseURL, "/") + "/" + LauncherAPIHost + "/api/launcher/resource/bootstrap.json"
values := url.Values{}
for _, key := range []string{"version", "file_path"} {
if value := query.Get(key); value != "" {
values.Set(key, value)
}
}
if encoded := values.Encode(); encoded != "" {
return base + "?" + encoded
}
return base
}
func (s *Server) launcherLatestVersion(snap *SnapshotSummary) string {
if snap.LauncherMetadata != nil && snap.LauncherMetadata.GameLatestVersion != "" {
return snap.LauncherMetadata.GameLatestVersion
}
return snap.AppVersion
}
func (s *Server) launcherLatestFilePath(snap *SnapshotSummary) string {
if snap.LauncherMetadata != nil {
return snap.LauncherMetadata.GameLatestFilePath
}
return ""
}
func (s *Server) launcherStartExeName(snap *SnapshotSummary) string {
if snap.LauncherMetadata != nil {
return snap.LauncherMetadata.GameStartExeName
}
return ""
}
func (s *Server) launcherStartParams(snap *SnapshotSummary) []string {
if snap.LauncherMetadata != nil && len(snap.LauncherMetadata.GameStartParams) > 0 {
return append([]string(nil), snap.LauncherMetadata.GameStartParams...)
}
return []string{}
}
func writeLauncherEnvelope[T any](w http.ResponseWriter, status int, code int, message string, data T) {
writeNoStoreJSON(w, status, LauncherEnvelope[T]{
Code: code,
Message: message,
Data: data,
})
}
func launcherHostPath(path string) string {
return fmt.Sprintf("/%s%s", LauncherAPIHost, path)
}