From 923883fad736f916aa03b1d411de3b8aeea78123 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 9 Apr 2016 13:40:44 -0400 Subject: [PATCH 1/2] Fix and improve custom rule detection. --- OpenRA.Game/GameRules/Ruleset.cs | 50 +++++++++++++++++++ OpenRA.Game/Map/MapPreview.cs | 28 +++++++++-- OpenRA.Game/Server/Server.cs | 2 +- OpenRA.Game/Traits/TraitsInterfaces.cs | 1 + .../ServerTraits/LobbyCommands.cs | 2 +- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/OpenRA.Game/GameRules/Ruleset.cs b/OpenRA.Game/GameRules/Ruleset.cs index ff1be28daf..db26035a65 100644 --- a/OpenRA.Game/GameRules/Ruleset.cs +++ b/OpenRA.Game/GameRules/Ruleset.cs @@ -200,5 +200,55 @@ namespace OpenRA return ruleset; } + + static bool AnyCustomYaml(MiniYaml yaml) + { + return yaml != null && (yaml.Value != null || yaml.Nodes.Any()); + } + + static bool AnyFlaggedTraits(ModData modData, List actors) + { + foreach (var actorNode in actors) + { + foreach (var traitNode in actorNode.Value.Nodes) + { + try + { + var traitName = traitNode.Key.Split('@')[0]; + var traitType = modData.ObjectCreator.FindType(traitName + "Info"); + if (traitType.GetInterface("ILobbyCustomRulesIgnore") == null) + return true; + } + catch { } + } + } + + return false; + } + + public static bool DefinesUnsafeCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, + MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications, MiniYaml mapSequences) + { + // Maps that define any weapon, voice, notification, or sequence overrides are always flagged + if (AnyCustomYaml(mapWeapons) || AnyCustomYaml(mapVoices) || AnyCustomYaml(mapNotifications) || AnyCustomYaml(mapSequences)) + return true; + + // Any trait overrides that aren't explicitly whitelisted are flagged + if (mapRules != null) + { + if (AnyFlaggedTraits(modData, mapRules.Nodes)) + return true; + + if (mapRules.Value != null) + { + var mapFiles = FieldLoader.GetValue("value", mapRules.Value); + foreach (var f in mapFiles) + if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f)))) + return true; + } + } + + return false; + } } } diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index e21fa9df54..8559ebd923 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -21,6 +21,7 @@ using System.Text; using System.Threading; using OpenRA.FileSystem; using OpenRA.Graphics; +using OpenRA.Primitives; namespace OpenRA { @@ -80,12 +81,14 @@ namespace OpenRA Lazy rules; public Ruleset Rules { get { return rules != null ? rules.Value : null; } } public bool InvalidCustomRules { get; private set; } + public bool DefinesUnsafeCustomRules { get; private set; } public bool RulesLoaded { get; private set; } - public void SetRulesetGenerator(ModData modData, Func generator) + public void SetRulesetGenerator(ModData modData, Func> generator) { InvalidCustomRules = false; RulesLoaded = false; + DefinesUnsafeCustomRules = false; // Note: multiple threads may try to access the value at the same time // We rely on the thread-safety guarantees given by Lazy to prevent race conitions. @@ -97,7 +100,9 @@ namespace OpenRA try { - return generator(); + var ret = generator(); + DefinesUnsafeCustomRules = ret.Second; + return ret.First; } catch (Exception e) { @@ -146,6 +151,15 @@ namespace OpenRA public Ruleset Rules { get { return innerData.Rules; } } public bool InvalidCustomRules { get { return innerData.InvalidCustomRules; } } public bool RulesLoaded { get { return innerData.RulesLoaded; } } + public bool DefinesUnsafeCustomRules + { + get + { + // Force lazy rules to be evaluated + var force = innerData.Rules; + return innerData.DefinesUnsafeCustomRules; + } + } Download download; public long DownloadBytes { get; private set; } @@ -297,8 +311,11 @@ namespace OpenRA var musicDefinitions = LoadRuleSection(yaml, "Music"); var notificationDefinitions = LoadRuleSection(yaml, "Notifications"); var sequenceDefinitions = LoadRuleSection(yaml, "Sequences"); - return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, + var rules = Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions); + var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions, + weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions); + return Pair.New(rules, flagged); }); if (p.Contains("map.png")) @@ -384,8 +401,11 @@ namespace OpenRA var musicDefinitions = LoadRuleSection(rulesYaml, "Music"); var notificationDefinitions = LoadRuleSection(rulesYaml, "Notifications"); var sequenceDefinitions = LoadRuleSection(rulesYaml, "Sequences"); - return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, + var rules = Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions); + var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions, + weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions); + return Pair.New(rules, flagged); }); } catch (Exception) { } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index a21e5fd497..41ab03c8a1 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -392,7 +392,7 @@ namespace OpenRA.Server SendOrderTo(newConn, "Message", motd); } - if (Map.Rules != ModData.DefaultRules && !LobbyInfo.IsSinglePlayer) + if (!LobbyInfo.IsSinglePlayer && Map.DefinesUnsafeCustomRules) SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change."); if (Settings.DisableSinglePlayer) diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index c6db694f5b..0411903902 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -319,6 +319,7 @@ namespace OpenRA.Traits public interface ITraitInfo : ITraitInfoInterface { object Create(ActorInitializer init); } public class TraitInfo : ITraitInfo where T : new() { public virtual object Create(ActorInitializer init) { return new T(); } } + public interface ILobbyCustomRulesIgnore { } [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:InterfaceNamesMustBeginWithI", Justification = "Not a real interface, but more like a tag.")] public interface Requires where T : class, ITraitInfoInterface { } diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index ac910a8c73..fef8907eaa 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -398,7 +398,7 @@ namespace OpenRA.Mods.Common.Server server.SendMessage("{0} changed the map to {1}.".F(client.Name, server.Map.Title)); - if (server.Map.Rules.Actors != server.ModData.DefaultRules.Actors) + if (server.Map.DefinesUnsafeCustomRules) server.SendMessage("This map contains custom rules. Game experience may change."); if (server.Settings.DisableSinglePlayer) From aa061a5bc6a10f2b23c4a55d3f1d9a73b35077f2 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 9 Apr 2016 13:41:08 -0400 Subject: [PATCH 2/2] Whitelist lighting and weather traits. --- .../Traits/PaletteEffects/GlobalLightingPaletteEffect.cs | 2 +- OpenRA.Mods.Common/Traits/World/WeatherOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/PaletteEffects/GlobalLightingPaletteEffect.cs b/OpenRA.Mods.Common/Traits/PaletteEffects/GlobalLightingPaletteEffect.cs index cf8edeed0d..6c8c2fffb4 100644 --- a/OpenRA.Mods.Common/Traits/PaletteEffects/GlobalLightingPaletteEffect.cs +++ b/OpenRA.Mods.Common/Traits/PaletteEffects/GlobalLightingPaletteEffect.cs @@ -17,7 +17,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Used for day/night effects.")] - class GlobalLightingPaletteEffectInfo : ITraitInfo + class GlobalLightingPaletteEffectInfo : ITraitInfo, ILobbyCustomRulesIgnore { [Desc("Do not modify graphics that use any palette in this list.")] public readonly HashSet ExcludePalettes = new HashSet { "cursor", "chrome", "colorpicker", "fog", "shroud", "alpha" }; diff --git a/OpenRA.Mods.Common/Traits/World/WeatherOverlay.cs b/OpenRA.Mods.Common/Traits/World/WeatherOverlay.cs index 7f96d1e383..88d8c99db8 100644 --- a/OpenRA.Mods.Common/Traits/World/WeatherOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/WeatherOverlay.cs @@ -17,7 +17,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Adds a particle-based overlay.")] - public class WeatherOverlayInfo : ITraitInfo + public class WeatherOverlayInfo : ITraitInfo, ILobbyCustomRulesIgnore { [Desc("Factor for particle density. As higher as more particles will get spawned.")] public readonly float ParticleDensityFactor = 0.0007625f;