#region Copyright & License Information /* * Copyright 2007-2021 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Threading; using OpenRA.Network; namespace OpenRA.Server { public interface ILintServerMapPass { void Run(Action emitError, Action emitWarning, ModData modData, MapPreview map, Ruleset mapRules); } public class MapStatusCache { readonly Dictionary cache = new Dictionary(); readonly Action onStatusChanged; readonly bool enableRemoteLinting; readonly ModData modData; public MapStatusCache(ModData modData, Action onStatusChanged, bool enableRemoteLinting) { this.modData = modData; this.enableRemoteLinting = enableRemoteLinting; this.onStatusChanged = onStatusChanged; } void RunLintTests(MapPreview map, Ruleset rules) { var status = cache[map]; var failed = false; Action onLintFailure = message => { Log.Write("server", "Map {0} failed lint with error: {1}", map.Title, message); failed = true; }; Action onLintWarning = _ => { }; foreach (var customMapPassType in modData.ObjectCreator.GetTypesImplementing()) { try { var customMapPass = (ILintServerMapPass)modData.ObjectCreator.CreateBasic(customMapPassType); customMapPass.Run(onLintFailure, onLintWarning, modData, map, rules); } catch (Exception e) { onLintFailure(e.ToString()); } } status &= ~Session.MapStatus.Validating; status |= failed ? Session.MapStatus.Incompatible : Session.MapStatus.Playable; cache[map] = status; onStatusChanged?.Invoke(map.Uid, status); } public Session.MapStatus this[MapPreview map] { get { if (cache.TryGetValue(map, out var status)) return status; Ruleset rules = null; try { rules = map.LoadRuleset(); // Locally installed maps are assumed to not require linting status = enableRemoteLinting && map.Status != MapStatus.Available ? Session.MapStatus.Validating : Session.MapStatus.Playable; if (map.DefinesUnsafeCustomRules()) status |= Session.MapStatus.UnsafeCustomRules; } catch (Exception e) { Log.Write("server", "Failed to load rules for `{0}` with error :{1}", map.Title, e.Message); status = Session.MapStatus.Incompatible; } if (map.Players.Players.Count > MapPlayers.MaximumPlayerCount) { Log.Write("server", "Failed to load `{0}`: Player count exceeds maximum ({1}/{2}).", map.Title, map.Players.Players.Count, MapPlayers.MaximumPlayerCount); status = Session.MapStatus.Incompatible; } cache[map] = status; if ((status & Session.MapStatus.Validating) != 0) ThreadPool.QueueUserWorkItem(_ => RunLintTests(map, rules)); return status; } } } }