feat: add UE4SS DLL runtime extension

This commit is contained in:
npc0-hue
2026-07-23 09:49:14 +08:00
parent 8e34c8762a
commit 1caf40565c
30 changed files with 1179 additions and 70 deletions
+114
View File
@@ -14,6 +14,8 @@ import (
var (
runtimeLogEventSchemaRefPattern = regexp.MustCompile(`^[A-Za-z0-9_./-]+\.json$`)
runtimeLogEventTypePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,119}$`)
runtimeDLLModKeyPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,79}$`)
runtimeDLLABIPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,80}$`)
)
func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles) error {
@@ -22,6 +24,8 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
lifecycleKeys := map[string]struct{}{}
transportKeys := map[string]struct{}{}
managerKeys := map[string]struct{}{}
dllExtensionKeys := map[string]struct{}{}
dllExtensionStates := map[string]string{}
discoveryKeys := map[string]struct{}{}
dependencyKeys := map[string]struct{}{}
installPlanKeys := map[string]struct{}{}
@@ -65,6 +69,10 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
if profile.ClientManagerRef != "" {
violations = append(violations, validateProfileKey(prefix+".clientManagerRef", profile.ClientManagerRef)...)
}
for j, key := range profile.DLLExtensionRefs {
violations = append(violations, validateProfileKey(fmt.Sprintf("%s.dllExtensionRefs[%d]", prefix, j), key)...)
}
violations = append(violations, duplicateViolations(prefix+".dllExtensionRefs", profile.DLLExtensionRefs)...)
violations = append(violations, validateRuntimePlatforms(prefix+".platforms", profile.Platforms)...)
}
for i, probe := range profiles.DependencyProbes {
@@ -349,6 +357,15 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
}
}
}
for i, extension := range profiles.DLLExtensions {
prefix := fmt.Sprintf("runtimeProfiles.dllExtensions[%d]", i)
violations = append(violations, validateProfileKey(prefix+".key", extension.Key)...)
violations = append(violations, recordRuntimeProfileKey(dllExtensionKeys, prefix+".key", extension.Key)...)
if extension.Key != "" {
dllExtensionStates[extension.Key] = extension.ReleaseState
}
violations = append(violations, validateRuntimeDLLExtensionProfile(prefix, extension)...)
}
for i, profile := range profiles.LifecycleProfiles {
for _, key := range profile.TransportKeys {
if _, ok := transportKeys[key]; !ok {
@@ -360,10 +377,107 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
violations = append(violations, fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d].clientManagerRef references undeclared client manager", i))
}
}
if len(profile.DLLExtensionRefs) > 0 {
if profile.Mode != "local-process" || !containsString(profile.Capabilities, domain.LifecycleCapabilityStart) || len(profile.Platforms) != 1 || profile.Platforms[0] != "windows" {
violations = append(violations, fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d] DLL extensions require a windows local-process start profile", i))
}
for _, key := range profile.DLLExtensionRefs {
state, exists := dllExtensionStates[key]
if !exists {
violations = append(violations, fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d].dllExtensionRefs references undeclared DLL extension %q", i, key))
continue
}
if state != "ready" {
violations = append(violations, fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d].dllExtensionRefs references unpublished DLL extension %q", i, key))
}
}
}
}
return finish(violations)
}
func validateRuntimeDLLExtensionProfile(prefix string, extension domain.RuntimeDLLExtensionProfile) []string {
var violations []string
if extension.Kind != "ue4ss-dll" || extension.Activation != "server-start" {
violations = append(violations, prefix+".kind and activation must be ue4ss-dll/server-start")
}
if !validSemanticVersion(extension.Version) {
violations = append(violations, prefix+".version must be semantic")
}
violations = append(violations, validateSafeRuntimeValue(prefix+".displayName", extension.DisplayName)...)
violations = append(violations, validateProfileKey(prefix+".targetKey", extension.TargetKey)...)
if !runtimeDLLModKeyPattern.MatchString(extension.ModKey) {
violations = append(violations, prefix+".modKey is invalid")
}
if extension.DLLRef != "ue4ss/Mods/"+extension.ModKey+"/dlls/main.dll" {
violations = append(violations, prefix+".dllRef must be the declared UE4SS main.dll path")
}
if extension.UpdateOnStart != true {
violations = append(violations, prefix+".updateOnStart must be true")
}
if extension.RCONPort < 1024 || extension.RCONPort > 65535 {
violations = append(violations, prefix+".rconPort must be an unprivileged port")
}
if len(extension.SupportedTargets) != 1 || extension.SupportedTargets[0].OS != "windows" || extension.SupportedTargets[0].Arch != "amd64" {
violations = append(violations, prefix+".supportedTargets must contain only windows/amd64")
}
if extension.ReleaseState != "ready" && extension.ReleaseState != "unpublished" {
violations = append(violations, prefix+".releaseState is invalid")
}
if extension.ReleaseURL != "" {
violations = append(violations, validateRuntimeDLLReleaseURL(prefix+".releaseUrl", extension.ReleaseURL)...)
}
if extension.ReleaseState == "ready" {
if extension.ReleaseURL == "" {
violations = append(violations, prefix+".releaseUrl is required for a ready release")
}
if !validSHA256Checksum(extension.Checksum) || !validSHA256Checksum(extension.SCUMExecutableChecksum) {
violations = append(violations, prefix+".checksum and scumExecutableChecksum must be SHA-256")
}
if extension.SizeBytes < 1 || extension.SizeBytes > 128*1024*1024 {
violations = append(violations, prefix+".sizeBytes is out of bounds")
}
if !runtimeDLLABIPattern.MatchString(extension.UE4SSABI) {
violations = append(violations, prefix+".ue4ssAbi is invalid")
}
}
return violations
}
func validateRuntimeDLLReleaseURL(field string, value string) []string {
parsed, err := url.Parse(value)
host := ""
if parsed != nil {
host = strings.ToLower(parsed.Hostname())
}
ip := net.ParseIP(host)
if err != nil || parsed == nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.User != nil || parsed.Fragment != "" || parsed.RawQuery != "" || parsed.Port() != "" && parsed.Port() != "443" || host == "localhost" || strings.HasSuffix(host, ".localhost") || strings.HasSuffix(host, ".local") || ip != nil && (ip.IsLoopback() || ip.IsPrivate() || ip.IsUnspecified() || ip.IsLinkLocalUnicast()) || !strings.HasSuffix(strings.ToLower(parsed.Path), ".dll") {
return []string{field + " must be a public credential-free HTTPS DLL URL"}
}
return nil
}
func validateRuntimeDLLExtensionPlan(prefix string, plan domain.RuntimeDLLExtensionPlan) []string {
return validateRuntimeDLLExtensionProfile(prefix, domain.RuntimeDLLExtensionProfile{
Key: plan.Key,
Kind: "ue4ss-dll",
Activation: "server-start",
Version: plan.Version,
ReleaseState: "ready",
ReleaseURL: plan.ReleaseURL,
Checksum: plan.Checksum,
SizeBytes: plan.SizeBytes,
TargetKey: plan.TargetKey,
ModKey: plan.ModKey,
DLLRef: plan.DLLRef,
SCUMExecutableChecksum: plan.SCUMExecutableChecksum,
UE4SSABI: plan.UE4SSABI,
SupportedTargets: []domain.RuntimeTarget{{OS: "windows", Arch: "amd64"}},
UpdateOnStart: true,
RCONPort: plan.RCONPort,
})
}
func validateProfileKey(field, value string) []string {
if strings.TrimSpace(value) == "" {
return []string{field + " is required"}