feat(all): configure proxies, reuse native HTTP and stabilize recovery

Persist direct/manual proxy settings in Client Studio and apply them to resource,
Unity and native requests, with loopback bypass and no automatic system proxy.
Pool WinHTTP/curl connections, bound workers and deadlines, cancel safely,
and log queue and transport timings without exposing sensitive paths.
Return explicit relay failures, probe through native HTTP, retry interrupted
requests and recognize completed home, field and pack scenes during recovery.
This commit is contained in:
2026-10-04 20:10:01 +08:00
parent aec053f7d2
commit e4276eb493
23 changed files with 1001 additions and 410 deletions
+31 -77
View File
@@ -1,89 +1,43 @@
package app
import (
"net"
"net/url"
"strings"
)
import "strings"
// Unity reads these variables before managed plugins can run.
func gameProxyEnvironment(environment []string, systemProxy string) []string {
result := append([]string(nil), environment...)
lookup := func(name string) (int, string) {
for index, entry := range result {
key, value, found := strings.Cut(entry, "=")
if found && strings.EqualFold(key, name) {
return index, value
}
}
return -1, ""
func isGameProxyEnvironmentKey(key string) bool {
switch strings.ToLower(key) {
case "unity_proxyserver", "unity_noproxy", "http_proxy", "https_proxy", "all_proxy", "no_proxy", "bd2_client_proxy_url":
return true
default:
return false
}
if index, _ := lookup("UNITY_PROXYSERVER"); index < 0 && validUnityProxy(systemProxy) {
result = append(result, "UNITY_PROXYSERVER="+systemProxy)
}
index, bypass := lookup("UNITY_NOPROXY")
parts := strings.FieldsFunc(bypass, func(character rune) bool { return character == ',' || character == ';' })
for _, local := range []string{"localhost", "127.0.0.1", "::1"} {
present := false
for _, part := range parts {
if strings.EqualFold(strings.TrimSpace(part), local) {
present = true
}
}
if !present {
parts = append(parts, local)
}
// Player settings are authoritative; stale process and OS proxy values cannot win.
func gameProxyEnvironment(environment []string, proxyURL string) []string {
result := make([]string, 0, len(environment)+12)
for _, entry := range environment {
key, _, _ := strings.Cut(entry, "=")
if !isGameProxyEnvironmentKey(key) {
result = append(result, entry)
}
}
entry := "UNITY_NOPROXY=" + strings.Join(parts, ",")
if index >= 0 {
result[index] = entry
} else {
result = append(result, entry)
for _, key := range []string{"UNITY_PROXYSERVER", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy", "BD2_CLIENT_PROXY_URL"} {
result = append(result, key+"="+proxyURL)
}
for _, key := range []string{"UNITY_NOPROXY", "NO_PROXY", "no_proxy"} {
result = append(result, key+"=localhost,127.0.0.1,::1")
}
return result
}
func validUnityProxy(proxy string) bool {
parsed, err := url.Parse(proxy)
if err != nil || parsed.Scheme != "http" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" || parsed.Path != "" {
return false
// LaunchServices needs explicit empty values when the player selects direct.
func gameOpenArguments(target string, environment []string, proxyURL string) []string {
args := []string{target}
for _, entry := range gameProxyEnvironment(environment, proxyURL) {
key, _, found := strings.Cut(entry, "=")
if found && isGameProxyEnvironmentKey(key) {
args = append(args, "--env", entry)
}
}
host, port, err := net.SplitHostPort(parsed.Host)
if err != nil || host == "" || port == "" {
return false
}
_, err = net.LookupPort("tcp", port)
return err == nil
}
// Unity accepts one proxy, so per-scheme configurations must agree.
func sharedWindowsProxy(raw string) string {
if !strings.Contains(raw, "=") {
proxy := "http://" + strings.TrimSpace(raw)
if validUnityProxy(proxy) {
return proxy
}
return ""
}
var httpProxy, httpsProxy string
for _, entry := range strings.Split(raw, ";") {
key, value, found := strings.Cut(strings.TrimSpace(entry), "=")
if !found {
return ""
}
switch strings.ToLower(key) {
case "http":
httpProxy = value
case "https":
httpsProxy = value
}
}
if httpProxy == "" || !strings.EqualFold(httpProxy, httpsProxy) {
return ""
}
proxy := "http://" + httpProxy
if validUnityProxy(proxy) {
return proxy
}
return ""
args = append(args, "--args")
return append(args, gameLaunchArguments()...)
}