feat: add UE4SS DLL runtime extension
This commit is contained in:
@@ -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"}
|
||||
|
||||
Reference in New Issue
Block a user