From eebac5198d30742caf8a5a73b1fad744a5cd01cb Mon Sep 17 00:00:00 2001 From: diaverso Date: Fri, 15 May 2026 18:48:30 +0200 Subject: [PATCH 1/2] feat: integrate community PRs #198, #230, #231, #246, #258 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FASTER/Models/ServerCfg.cs - #246: Wrap LogObjectNotFound, SkipDescriptionParsing, ignoreMissionLoadErrors inside a class AdvancedOptions { }; block in server.cfg output FASTER/Models/BasicCfg.cs - #230: Add configurable language setting (default "English") with RaisePropertyChanged; replace hardcoded language="English" in ProcessFile() FASTER/Models/ServerProfile.cs - #231: Add HugePages (bool), BePath (string), ExThreads (int 0-7), LoadMissionToMemory (bool), LimitFPS (int), EnableSteamLogs (bool) properties with conditional inclusion in GetCommandLine() FASTER/ViewModel/ProfileViewModel.cs - #230: Expose Languages observable collection from BasicCfgArrays - #231: Add SelectBePath() folder picker FASTER/Views/Profile.xaml, FASTER/Views/Profile.xaml.cs - #230: Language ComboBox in Performance tab - #231: Controls for HugePages, BePath, ExThreads, LoadMissionToMemory, LimitFPS, EnableSteamLogs FASTER/ViewModel/ModsViewModel.cs, FASTER/Views/Mods.xaml, FASTER/Views/Mods.xaml.cs - #198: Add PurgeAndReinstallMod(), PurgeAndReinstallSelectedMods(), PurgeAndReinstallAll() — delete mod folder and mark for re-download - #258: Make CheckForUpdates() async with 300ms delay between mods to avoid Steam rate limiting Co-Authored-By: Claude Sonnet 4.6 --- FASTER/Models/BasicCfg.cs | 41 +++-- FASTER/Models/ServerCfg.cs | 235 +++++++++++++++++---------- FASTER/Models/ServerProfile.cs | 95 ++++++++++- FASTER/ViewModel/ModsViewModel.cs | 121 +++++++++++++- FASTER/ViewModel/ProfileViewModel.cs | 49 ++++++ FASTER/Views/Mods.xaml | 13 +- FASTER/Views/Mods.xaml.cs | 19 ++- FASTER/Views/Profile.xaml | 64 +++++++- FASTER/Views/Profile.xaml.cs | 10 ++ 9 files changed, 536 insertions(+), 111 deletions(-) diff --git a/FASTER/Models/BasicCfg.cs b/FASTER/Models/BasicCfg.cs index fa3cf7f..dbdd699 100644 --- a/FASTER/Models/BasicCfg.cs +++ b/FASTER/Models/BasicCfg.cs @@ -9,6 +9,7 @@ public static class BasicCfgArrays { public static string[] PerfPresets { get; } = {"Custom", "Arma3 Defaults", "1Mb Preset", "250Mb Preset", "1Gb Preset"}; public static double[] TerrainGrids { get; } = { 50, 25, 12.5, 6.25, 3.125 }; + public static string[] Languages { get; } = { "English", "Czech", "French", "German", "Italian", "Polish", "Portuguese", "Russian", "Spanish", "Turkish", "Hungarian" }; } [Serializable] @@ -27,15 +28,26 @@ public class BasicCfg : INotifyPropertyChanged private ushort maxCustomFileSize = 1024; private ushort maxPacketSize = 1400; + private string _language = "English"; private string basicContent; + public string Language + { + get => _language; + set + { + _language = value; + RaisePropertyChanged(nameof(Language)); + } + } + public string BasicContent { get => basicContent; set { basicContent = value; - RaisePropertyChanged("BasicContent"); + RaisePropertyChanged(nameof(BasicContent)); } } @@ -45,7 +57,7 @@ public uint ViewDistance set { viewDistance = value; - RaisePropertyChanged("ViewDistance"); + RaisePropertyChanged(nameof(ViewDistance)); } } @@ -55,7 +67,7 @@ public double TerrainGrid set { terrainGrid = value; - RaisePropertyChanged("TerrainGrid"); + RaisePropertyChanged(nameof(TerrainGrid)); } } @@ -65,7 +77,7 @@ public ushort MaxSizeGuaranteed set { maxSizeGuaranteed = value; - RaisePropertyChanged("MaxSizeGuaranteed"); + RaisePropertyChanged(nameof(MaxSizeGuaranteed)); } } @@ -75,7 +87,7 @@ public ushort MaxSizeNonGuaranteed set { maxSizeNonguaranteed = value; - RaisePropertyChanged("MaxSizeNonGuaranteed"); + RaisePropertyChanged(nameof(MaxSizeNonGuaranteed)); } } @@ -85,7 +97,7 @@ public ushort MaxMsgSend set { maxMsgSend = value; - RaisePropertyChanged("MaxMsgSend"); + RaisePropertyChanged(nameof(MaxMsgSend)); } } @@ -95,7 +107,7 @@ public ulong MinBandwidth set { minBandwidth = value; - RaisePropertyChanged("MinBandwidth"); + RaisePropertyChanged(nameof(MinBandwidth)); } } @@ -105,7 +117,7 @@ public ulong MaxBandwidth set { maxBandwidth = value; - RaisePropertyChanged("MaxBandwidth"); + RaisePropertyChanged(nameof(MaxBandwidth)); } } @@ -115,7 +127,7 @@ public ushort MaxPacketSize set { maxPacketSize = value; - RaisePropertyChanged("MaxPacketSize"); + RaisePropertyChanged(nameof(MaxPacketSize)); } } @@ -125,7 +137,7 @@ public double MinErrorToSend set { minErrorToSend = value; - RaisePropertyChanged("MinErrorToSend"); + RaisePropertyChanged(nameof(MinErrorToSend)); } } @@ -135,7 +147,7 @@ public double MinErrorToSendNear set { minErrorToSendNear = value; - RaisePropertyChanged("MinErrorToSendNear"); + RaisePropertyChanged(nameof(MinErrorToSendNear)); } } @@ -145,11 +157,12 @@ public ushort MaxCustomFileSize set { maxCustomFileSize = value; - RaisePropertyChanged("MaxCustomFileSize"); + RaisePropertyChanged(nameof(MaxCustomFileSize)); } } [XmlIgnore] + [Newtonsoft.Json.JsonIgnore] public string PerfPreset { get => "Custom"; @@ -181,7 +194,7 @@ public string PerfPreset MinBandwidth = 1000000000; break; } - RaisePropertyChanged("PerfPreset"); + RaisePropertyChanged(nameof(PerfPreset)); } } @@ -191,7 +204,7 @@ public BasicCfg() public string ProcessFile() { string output = "// These options are created by default\r\n" - + "language=\"English\";\r\n" + + $"language=\"{_language}\";\r\n" + "adapter=-1;\r\n" + "3D_Performance=1.000000;\r\n" + "Resolution_W=800;\r\n" diff --git a/FASTER/Models/ServerCfg.cs b/FASTER/Models/ServerCfg.cs index d5f14f3..d79c292 100644 --- a/FASTER/Models/ServerCfg.cs +++ b/FASTER/Models/ServerCfg.cs @@ -50,8 +50,8 @@ public class ServerCfg : INotifyPropertyChanged private int roleTimeOut = 90; // <- These are BI base figues private int votingTimeOut = 60; // <- private int debriefingTimeOut = 45; // <- - private bool LogObjectNotFound = true; // logging enabled - private bool SkipDescriptionParsing = false; // parse description.ext + private bool _logObjectNotFound = true; // logging enabled + private bool _skipDescriptionParsing = false; // parse description.ext private bool ignoreMissionLoadErrors = false; // do not ingore errors private int armaUnitsTimeout = 30; // Defines how long the player will be stuck connecting and wait for armaUnits data. Player will be notified if timeout elapsed and no units data was received private int queueSizeLogG = 1000000; // if a specific players message queue is larger than 1MB and '#monitor' is running, dump his messages to a logfile for analysis @@ -88,6 +88,14 @@ public class ServerCfg : INotifyPropertyChanged private List _missions = new(); private bool autoInit; private string difficulty = "Custom"; + private string _missionHTTPDownloadBaseURL = ""; + + // AntiFlood (Arma 2.18+) + private bool _antiFloodEnabled = false; + private int _antiFloodCycleTime = 5; + private int _antiFloodCycleLimit = 5; + private int _antiFloodCycleHardLimit = 10; + private int _antiFloodEnableKick = 0; // 0 = disabled, 1 = enabled private bool maxMemOverride; private uint maxMem = 1024; @@ -106,7 +114,7 @@ public string PasswordAdmin set { passwordAdmin = value; - RaisePropertyChanged("PasswordAdmin"); + RaisePropertyChanged(nameof(PasswordAdmin)); } } @@ -116,7 +124,7 @@ public string Password set { password = value; - RaisePropertyChanged("Password"); + RaisePropertyChanged(nameof(Password)); } } @@ -126,7 +134,7 @@ public string ServerCommandPassword set { serverCommandPassword = value; - RaisePropertyChanged("ServerCommandPassword"); + RaisePropertyChanged(nameof(ServerCommandPassword)); } } @@ -136,7 +144,7 @@ public string Hostname set { hostname = value; - RaisePropertyChanged("Hostname"); + RaisePropertyChanged(nameof(Hostname)); } } @@ -146,7 +154,7 @@ public int MaxPlayers set { maxPlayers = value; - RaisePropertyChanged("MaxPlayers"); + RaisePropertyChanged(nameof(MaxPlayers)); } } @@ -156,7 +164,7 @@ public string Motd set { motd = value.Replace("\r", "").Split('\n').ToList(); - RaisePropertyChanged("Motd"); + RaisePropertyChanged(nameof(Motd)); } } @@ -166,7 +174,7 @@ public int MotdInterval set { motdInterval = value; - RaisePropertyChanged("MotdInterval"); + RaisePropertyChanged(nameof(MotdInterval)); } } @@ -176,7 +184,7 @@ public string Admins set { admins = value.Replace("\r", "").Split('\n').ToList(); - RaisePropertyChanged("Admins"); + RaisePropertyChanged(nameof(Admins)); } } @@ -186,7 +194,7 @@ public string HeadlessClients set { headlessClients = value.Replace("\r", "").Split('\n').ToList(); - RaisePropertyChanged("HeadlessClients"); + RaisePropertyChanged(nameof(HeadlessClients)); } } @@ -196,7 +204,7 @@ public string LocalClient set { localClient = value.Split('\n').ToList(); - RaisePropertyChanged("LocalClient"); + RaisePropertyChanged(nameof(LocalClient)); } } @@ -206,7 +214,7 @@ public bool HeadlessClientEnabled set { headlessClientEnabled = value; - RaisePropertyChanged("HeadlessClientEnabled"); + RaisePropertyChanged(nameof(HeadlessClientEnabled)); if (value && !headlessClients.Exists(e => e.Length > 0)) { HeadlessClients = "127.0.0.1"; } } @@ -218,7 +226,7 @@ public bool VotingEnabled set { votingEnabled = value; - RaisePropertyChanged("VotingEnabled"); + RaisePropertyChanged(nameof(VotingEnabled)); } } @@ -228,7 +236,7 @@ public bool NetLogEnabled set { netlogEnabled = value; - RaisePropertyChanged("NetLogEnabled"); + RaisePropertyChanged(nameof(NetLogEnabled)); } } #endregion @@ -240,7 +248,7 @@ public double VoteThreshold set { voteThreshold = value; - RaisePropertyChanged("VoteThreshold"); + RaisePropertyChanged(nameof(VoteThreshold)); } } @@ -250,7 +258,7 @@ public int VoteMissionPlayers set { voteMissionPlayers = value; - RaisePropertyChanged("VoteMissionPlayers"); + RaisePropertyChanged(nameof(VoteMissionPlayers)); } } @@ -260,7 +268,7 @@ public bool KickDuplicates set { kickduplicate = value ? (short)1 : (short)0; - RaisePropertyChanged("KickDuplicates"); + RaisePropertyChanged(nameof(KickDuplicates)); } } @@ -270,7 +278,7 @@ public bool Loopback set { loopback = value; - RaisePropertyChanged("Loopback"); + RaisePropertyChanged(nameof(Loopback)); } } @@ -280,7 +288,7 @@ public bool Upnp set { upnp = value; - RaisePropertyChanged("Upnp"); + RaisePropertyChanged(nameof(Upnp)); } } @@ -290,7 +298,7 @@ public string AllowedFilePatching set { allowedFilePatching = (short)Array.IndexOf(ServerCfgArrays.AllowFilePatchingStrings, value); - RaisePropertyChanged("Password"); + RaisePropertyChanged(nameof(AllowedFilePatching)); } } @@ -300,7 +308,7 @@ public int DisconnectTimeout set { disconnectTimeout = value; - RaisePropertyChanged("DisconnectTimeout"); + RaisePropertyChanged(nameof(DisconnectTimeout)); } } @@ -310,7 +318,7 @@ public int MaxDesync set { maxdesync = value; - RaisePropertyChanged("MaxDesync"); + RaisePropertyChanged(nameof(MaxDesync)); } } @@ -320,7 +328,7 @@ public int MaxPing set { maxping = value; - RaisePropertyChanged("MaxPing"); + RaisePropertyChanged(nameof(MaxPing)); } } @@ -330,7 +338,7 @@ public int MaxPacketLoss set { maxpacketloss = value; - RaisePropertyChanged("MaxPacketLoss"); + RaisePropertyChanged(nameof(MaxPacketLoss)); } } @@ -340,7 +348,7 @@ public bool KickClientOnSlowNetwork set { kickClientOnSlowNetwork = value; - RaisePropertyChanged("KickClientOnSlowNetwork"); + RaisePropertyChanged(nameof(KickClientOnSlowNetwork)); } } @@ -350,7 +358,7 @@ public int LobbyIdleTimeout set { lobbyIdleTimeout = value; - RaisePropertyChanged("LobbyIdleTimeout"); + RaisePropertyChanged(nameof(LobbyIdleTimeout)); } } @@ -360,7 +368,7 @@ public int BriefingTimeOut set { briefingTimeOut = value; - RaisePropertyChanged("BriefingTimeOut"); + RaisePropertyChanged(nameof(BriefingTimeOut)); } } @@ -370,7 +378,7 @@ public int RoleTimeOut set { roleTimeOut = value; - RaisePropertyChanged("RoleTimeOut"); + RaisePropertyChanged(nameof(RoleTimeOut)); } } @@ -380,7 +388,7 @@ public int VotingTimeOut set { votingTimeOut = value; - RaisePropertyChanged("VotingTimeOut"); + RaisePropertyChanged(nameof(VotingTimeOut)); } } @@ -390,27 +398,27 @@ public int DebriefingTimeOut set { debriefingTimeOut = value; - RaisePropertyChanged("DebriefingTimeOut"); + RaisePropertyChanged(nameof(DebriefingTimeOut)); } } public bool logObjectNotFound { - get => LogObjectNotFound; + get => _logObjectNotFound; set { - LogObjectNotFound = value; - RaisePropertyChanged("logObjectNotFound"); + _logObjectNotFound = value; + RaisePropertyChanged(nameof(logObjectNotFound)); } } public bool skipDescriptionParsing { - get => SkipDescriptionParsing; + get => _skipDescriptionParsing; set { - SkipDescriptionParsing = value; - RaisePropertyChanged("skipDescriptionParsing"); + _skipDescriptionParsing = value; + RaisePropertyChanged(nameof(skipDescriptionParsing)); } } @@ -420,7 +428,7 @@ public bool IgnoreMissionLoadErrors set { ignoreMissionLoadErrors = value; - RaisePropertyChanged("IgnoreMissionLoadErrors"); + RaisePropertyChanged(nameof(IgnoreMissionLoadErrors)); } } @@ -430,7 +438,7 @@ public int ArmaUnitsTimeout set { armaUnitsTimeout = value; - RaisePropertyChanged("ArmaUnitsTimeout"); + RaisePropertyChanged(nameof(ArmaUnitsTimeout)); } } @@ -440,7 +448,7 @@ public int QueueSizeLogG set { queueSizeLogG = value; - RaisePropertyChanged("QueueSizeLogG"); + RaisePropertyChanged(nameof(QueueSizeLogG)); } } @@ -450,7 +458,7 @@ public string ForcedDifficulty set { forcedDifficulty = value; - RaisePropertyChanged("ForcedDifficulty"); + RaisePropertyChanged(nameof(ForcedDifficulty)); } } @@ -460,7 +468,7 @@ public bool AutoSelectMission set { autoSelectMission = value; - RaisePropertyChanged("AutoSelectMission"); + RaisePropertyChanged(nameof(AutoSelectMission)); } } @@ -470,7 +478,67 @@ public bool RandomMissionOrder set { randomMissionOrder = value; - RaisePropertyChanged("RandomMissionOrder"); + RaisePropertyChanged(nameof(RandomMissionOrder)); + } + } + + public string MissionHTTPDownloadBaseURL + { + get => _missionHTTPDownloadBaseURL; + set + { + _missionHTTPDownloadBaseURL = value; + RaisePropertyChanged(nameof(MissionHTTPDownloadBaseURL)); + } + } + + public bool AntiFloodEnabled + { + get => _antiFloodEnabled; + set + { + _antiFloodEnabled = value; + RaisePropertyChanged(nameof(AntiFloodEnabled)); + } + } + + public int AntiFloodCycleTime + { + get => _antiFloodCycleTime; + set + { + _antiFloodCycleTime = value; + RaisePropertyChanged(nameof(AntiFloodCycleTime)); + } + } + + public int AntiFloodCycleLimit + { + get => _antiFloodCycleLimit; + set + { + _antiFloodCycleLimit = value; + RaisePropertyChanged(nameof(AntiFloodCycleLimit)); + } + } + + public int AntiFloodCycleHardLimit + { + get => _antiFloodCycleHardLimit; + set + { + _antiFloodCycleHardLimit = value; + RaisePropertyChanged(nameof(AntiFloodCycleHardLimit)); + } + } + + public bool AntiFloodEnableKick + { + get => _antiFloodEnableKick == 1; + set + { + _antiFloodEnableKick = value ? 1 : 0; + RaisePropertyChanged(nameof(AntiFloodEnableKick)); } } #endregion @@ -482,7 +550,7 @@ public string VerifySignatures set { verifySignatures = (short)Array.IndexOf(ServerCfgArrays.VerifySignaturesStrings, value); - RaisePropertyChanged("VerifySignatures"); + RaisePropertyChanged(nameof(VerifySignatures)); } } @@ -492,7 +560,7 @@ public bool DrawingInMap set { drawingInMap = value; - RaisePropertyChanged("DrawinginMap"); + RaisePropertyChanged(nameof(DrawingInMap)); } } @@ -502,7 +570,7 @@ public bool VonActivated set { disableVoN = value ? (short)0 : (short)1; - RaisePropertyChanged("VonActivated"); + RaisePropertyChanged(nameof(VonActivated)); } } @@ -512,7 +580,7 @@ public int VonCodecQuality set { vonCodecQuality = value; - RaisePropertyChanged("VonCodecQuality"); + RaisePropertyChanged(nameof(VonCodecQuality)); } } @@ -522,7 +590,7 @@ public string VonCodec set { vonCodec = (short)Array.IndexOf(ServerCfgArrays.VonCodecStrings, value); - RaisePropertyChanged("VonCodec"); + RaisePropertyChanged(nameof(VonCodec)); } } @@ -532,7 +600,7 @@ public bool SkipLobby set { skipLobby = value; - RaisePropertyChanged("SkipLobby"); + RaisePropertyChanged(nameof(SkipLobby)); } } @@ -542,7 +610,7 @@ public string LogFile set { logFile = value; - RaisePropertyChanged("LogFile"); + RaisePropertyChanged(nameof(LogFile)); } } @@ -552,7 +620,7 @@ public bool BattlEye set { battlEye = value ? (short)1 : (short)0; - RaisePropertyChanged("BattlEye"); + RaisePropertyChanged(nameof(BattlEye)); } } @@ -562,7 +630,7 @@ public string TimeStampFormat set { timeStampFormat = value; - RaisePropertyChanged("TimeStampFormat"); + RaisePropertyChanged(nameof(TimeStampFormat)); } } @@ -574,7 +642,7 @@ public bool Persistent persistent = value ? (short)1 : (short)0; if (!value) AutoInit = false; - RaisePropertyChanged("Persistent"); + RaisePropertyChanged(nameof(Persistent)); } } @@ -584,7 +652,7 @@ public bool RequiredBuildChecked set { requiredBuildChecked = value; - RaisePropertyChanged("RequiredBuildChecked"); + RaisePropertyChanged(nameof(RequiredBuildChecked)); } } @@ -594,7 +662,7 @@ public int RequiredBuild set { requiredBuild = value; - RaisePropertyChanged("RequiredBuild"); + RaisePropertyChanged(nameof(RequiredBuild)); } } @@ -604,7 +672,7 @@ public int SteamProtocolMaxDataSize set { steamProtocolMaxDataSize = value; - RaisePropertyChanged("SteamProtocolMaxDataSize"); + RaisePropertyChanged(nameof(SteamProtocolMaxDataSize)); } } #endregion @@ -617,7 +685,7 @@ public string DoubleIdDetected set { doubleIdDetected = value; - RaisePropertyChanged("DoubleIdDetected"); + RaisePropertyChanged(nameof(DoubleIdDetected)); } } @@ -627,7 +695,7 @@ public string OnUserConnected set { onUserConnected = value; - RaisePropertyChanged("OnUserConnected"); + RaisePropertyChanged(nameof(OnUserConnected)); } } @@ -637,7 +705,7 @@ public string OnUserDisconnected set { onUserDisconnected = value; - RaisePropertyChanged("OnUserDisconnected"); + RaisePropertyChanged(nameof(OnUserDisconnected)); } } @@ -647,7 +715,7 @@ public string OnHackedData set { onHackedData = value; - RaisePropertyChanged("OnHackedData"); + RaisePropertyChanged(nameof(OnHackedData)); } } @@ -657,7 +725,7 @@ public string OnDifferentData set { onDifferentData = value; - RaisePropertyChanged("OnDifferentData"); + RaisePropertyChanged(nameof(OnDifferentData)); } } @@ -667,7 +735,7 @@ public string OnUnsignedData set { onUnsignedData = value; - RaisePropertyChanged("OnUnsignedData"); + RaisePropertyChanged(nameof(OnUnsignedData)); } } @@ -677,7 +745,7 @@ public string OnUserKicked set { onUserKicked = value; - RaisePropertyChanged("OnUserKicked"); + RaisePropertyChanged(nameof(OnUserKicked)); } } #endregion @@ -689,7 +757,7 @@ public bool MissionChecked set { missionSelectorChecked = value; - RaisePropertyChanged("MissionChecked"); + RaisePropertyChanged(nameof(MissionChecked)); } } @@ -699,7 +767,7 @@ public string MissionContentOverride set { missionContentOverride = value; - RaisePropertyChanged("MissionContentOverride"); + RaisePropertyChanged(nameof(MissionContentOverride)); } } @@ -709,7 +777,7 @@ public bool AutoInit set { autoInit = value; - RaisePropertyChanged("AutoInit"); + RaisePropertyChanged(nameof(AutoInit)); } } @@ -719,7 +787,7 @@ public string Difficulty set { difficulty = value; - RaisePropertyChanged("Difficulty"); + RaisePropertyChanged(nameof(Difficulty)); } } @@ -740,7 +808,7 @@ public List Missions if (!isEqual) { _missions = value; - RaisePropertyChanged("Missions"); + RaisePropertyChanged(nameof(Missions)); } //Adding the trigger to count checked mods @@ -756,7 +824,7 @@ public bool MaxMemOverride set { maxMemOverride = value; - RaisePropertyChanged("MaxMemOverride"); + RaisePropertyChanged(nameof(MaxMemOverride)); } } @@ -766,7 +834,7 @@ public bool CpuCountOverride set { cpuCountOverride = value; - RaisePropertyChanged("CpuCountOverride"); + RaisePropertyChanged(nameof(CpuCountOverride)); } } @@ -776,7 +844,7 @@ public uint MaxMem set { maxMem = value; - RaisePropertyChanged("MaxMem"); + RaisePropertyChanged(nameof(MaxMem)); } } @@ -786,7 +854,7 @@ public ushort CpuCount set { cpuCount = value; - RaisePropertyChanged("CpuCount"); + RaisePropertyChanged(nameof(CpuCount)); } } @@ -796,7 +864,7 @@ public string CommandLineParameters set { commandLineParams = value; - RaisePropertyChanged("CommandLineParameters"); + RaisePropertyChanged(nameof(CommandLineParameters)); } } @@ -808,7 +876,7 @@ public string ServerCfgContent set { serverCfgContent = value; - RaisePropertyChanged("ServerCfgContent"); + RaisePropertyChanged(nameof(ServerCfgContent)); } } @@ -820,8 +888,8 @@ public ServerCfg() private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) { - RaisePropertyChanged("MissionChecked"); - RaisePropertyChanged("MissionContentOverride"); + RaisePropertyChanged(nameof(MissionChecked)); + RaisePropertyChanged(nameof(MissionContentOverride)); } public string ProcessFile() @@ -893,10 +961,9 @@ public string ProcessFile() + $"timeStampFormat = \"{timeStampFormat}\";\t\t// Set the timestamp format used on each report line in server-side RPT file. Possible values are \"none\" (default),\"short\",\"full\".\r\n" + $"BattlEye = {battlEye};\t\t\t\t// Server to use BattlEye system\r\n" + $"queueSizeLogG = {queueSizeLogG};\t\t\t// If a specific players message queue is larger than 1MB and #monitor is running, dump his messages to a logfile for analysis \r\n" - + $"LogObjectNotFound = {logObjectNotFound};\t\t// When false to skip logging 'Server: Object not found messages'.\r\n" - + $"SkipDescriptionParsing = {skipDescriptionParsing};\t\t// When true to skip parsing of description.ext/mission.sqm. Will show pbo filename instead of configured missionName. OverviewText and such won't work, but loading the mission list is a lot faster when there are many missions \r\n" + + $"class AdvancedOptions\r\n{{\r\n\tLogObjectNotFound = {logObjectNotFound};\t\t// When false to skip logging 'Server: Object not found messages'.\r\n\tSkipDescriptionParsing = {skipDescriptionParsing};\t\t// When true to skip parsing of description.ext/mission.sqm. Will show pbo filename instead of configured missionName. OverviewText and such won't work, but loading the mission list is a lot faster when there are many missions.\r\n}};\r\n" + $"ignoreMissionLoadErrors = {ignoreMissionLoadErrors};\t\t// When set to true, the mission will load no matter the amount of loading errors. If set to false, the server will abort mission's loading and return to mission selection.\r\n" - + $"forcedDifficulty = {forcedDifficulty};\t\t\t// Forced difficulty (Recruit, Regular, Veteran, Custom)\r\n" + + $"forcedDifficulty = \"{forcedDifficulty}\";\t\t\t// Forced difficulty (Recruit, Regular, Veteran, Custom)\r\n" + "\r\n" + "// TIMEOUTS\r\n" + $"disconnectTimeout = {disconnectTimeout};\t\t\t// Time to wait before disconnecting a user which temporarly lost connection. Range is 5 to 90 seconds.\r\n" @@ -925,6 +992,7 @@ public string ProcessFile() + "// MISSIONS CYCLE (see below)\r\n" + $"randomMissionOrder = {randomMissionOrder};\t\t// Randomly iterate through Missions list\r\n" + $"autoSelectMission = {autoSelectMission};\t\t\t// Server auto selects next mission in cycle\r\n" + + (!string.IsNullOrWhiteSpace(MissionHTTPDownloadBaseURL) ? $"missionHTTPDownloadBaseURL = \"{MissionHTTPDownloadBaseURL}\";\r\n" : "") + "\r\n" + $"{MissionContentOverride}\t\t\t\t\t// An empty Missions class means there will be no mission rotation\r\n" + "\r\n" @@ -933,7 +1001,8 @@ public string ProcessFile() + "\r\n" + "// HEADLESS CLIENT\r\n" + $"{(headlessClientEnabled && !headlessClients.Exists(string.IsNullOrWhiteSpace) ? $"headlessClients[] = { "{\n\t\"" + string.Join("\",\n\t \"", headlessClients) + "\"\n}" };\r\n" : "")}" - + $"{(headlessClientEnabled && !localClient.Exists(string.IsNullOrWhiteSpace)? $"localClient[] = { "{\n\t\"" + string.Join("\",\n\t \"", localClient) + "\"\n}" };" : "")}"; + + $"{(headlessClientEnabled && !localClient.Exists(string.IsNullOrWhiteSpace)? $"localClient[] = { "{\n\t\"" + string.Join("\",\n\t \"", localClient) + "\"\n}" };" : "")}" + + (AntiFloodEnabled ? $"class AntiFlood\r\n{{\r\n\tcycleTime = {AntiFloodCycleTime};\r\n\tcycleLimit = {AntiFloodCycleLimit};\r\n\tcycleHardLimit = {AntiFloodCycleHardLimit};\r\n\tenableKick = {_antiFloodEnableKick};\r\n}};\r\n" : ""); return output; } @@ -960,7 +1029,7 @@ public bool MissionChecked set { missionChecked = value; - RaisePropertyChanged("MissionChecked"); + RaisePropertyChanged(nameof(MissionChecked)); } } @@ -970,7 +1039,7 @@ public string Name set { name = value; - RaisePropertyChanged("Name"); + RaisePropertyChanged(nameof(Name)); } } @@ -980,7 +1049,7 @@ public string Path set { path = value; - RaisePropertyChanged("Path"); + RaisePropertyChanged(nameof(Path)); } } diff --git a/FASTER/Models/ServerProfile.cs b/FASTER/Models/ServerProfile.cs index c9015a4..48fa55b 100644 --- a/FASTER/Models/ServerProfile.cs +++ b/FASTER/Models/ServerProfile.cs @@ -1,4 +1,4 @@ - + using System; using System.Collections.Generic; using System.ComponentModel; @@ -64,6 +64,13 @@ public class ServerProfile : INotifyPropertyChanged private bool _efDlcChecked; private bool _enableHT = true; private bool _enableRanking; + private bool _hugePages = false; + private string _bePath = ""; + private string _keysFolder = ""; + private int _exThreads = 0; + private bool _loadMissionToMemory = false; + private int _limitFPS = 0; + private bool _enableSteamLogs = false; private List _profileMods = new List(); private string _profileModsFilter = ""; @@ -94,7 +101,14 @@ public string Name _name = value; if (MainWindow.HasLoaded()) { - var menuItem = MainWindow.Instance.IServerProfilesMenu.Items.Cast().FirstOrDefault(p => p.Name == _id); + ToggleButton menuItem = null; + foreach (var item in MainWindow.Instance.IServerProfilesMenu.Items) + { + ToggleButton tb = item is System.Windows.Controls.DockPanel dp + ? dp.Children.OfType().FirstOrDefault() + : item as ToggleButton; + if (tb?.Name == _id) { menuItem = tb; break; } + } if (menuItem != null) { menuItem.Content = _name; } } @@ -245,6 +259,76 @@ public bool RankingChecked } } + public bool HugePages + { + get => _hugePages; + set + { + _hugePages = value; + RaisePropertyChanged(nameof(HugePages)); + } + } + + public string BePath + { + get => _bePath; + set + { + _bePath = value; + RaisePropertyChanged(nameof(BePath)); + } + } + + public string KeysFolder + { + get => _keysFolder; + set + { + _keysFolder = value; + RaisePropertyChanged(nameof(KeysFolder)); + } + } + + public int ExThreads + { + get => _exThreads; + set + { + _exThreads = value; + RaisePropertyChanged(nameof(ExThreads)); + } + } + + public bool LoadMissionToMemory + { + get => _loadMissionToMemory; + set + { + _loadMissionToMemory = value; + RaisePropertyChanged(nameof(LoadMissionToMemory)); + } + } + + public int LimitFPS + { + get => _limitFPS; + set + { + _limitFPS = value; + RaisePropertyChanged(nameof(LimitFPS)); + } + } + + public bool EnableSteamLogs + { + get => _enableSteamLogs; + set + { + _enableSteamLogs = value; + RaisePropertyChanged(nameof(EnableSteamLogs)); + } + } + //Current logic to count the checked mods public int ServerModsChecked => ProfileMods.Count(m => m.ServerSideChecked); public int ClientModsChecked => ProfileMods.Count(m => m.ClientSideChecked); @@ -547,6 +631,13 @@ private string GetCommandLine() $"{(ServerCfg.AutoInit ? " -autoInit" : "")}", $"{(ServerCfg.MaxMemOverride ? $" -maxMem={ServerCfg.MaxMem}" : "")}", $"{(ServerCfg.CpuCountOverride ? $" -cpuCount={ServerCfg.CpuCount}" : "")}", + $"{(HugePages ? " -hugePages" : "")}", + $"{(!string.IsNullOrWhiteSpace(BePath) ? $" \"-bepath={BePath}\"" : "")}", + $"{(!string.IsNullOrWhiteSpace(KeysFolder) ? $" \"-keysFolder={KeysFolder}\"" : "")}", + $"{(ExThreads > 0 ? $" -exThreads={ExThreads}" : "")}", + $"{(LoadMissionToMemory ? " -loadMissionToMemory" : "")}", + $"{(LimitFPS > 0 ? $" -limitFPS={LimitFPS}" : "")}", + $"{(EnableSteamLogs ? " -enableSteamLogs" : "")}", $"{(!string.IsNullOrWhiteSpace(ServerCfg.CommandLineParameters) ? $" {ServerCfg.CommandLineParameters}" : "")}" }; diff --git a/FASTER/ViewModel/ModsViewModel.cs b/FASTER/ViewModel/ModsViewModel.cs index f560b6e..f2f5f25 100644 --- a/FASTER/ViewModel/ModsViewModel.cs +++ b/FASTER/ViewModel/ModsViewModel.cs @@ -230,10 +230,16 @@ public void OpenModFolder(ArmaMod mod) Process.Start(startInfo); } - public void CheckForUpdates() + public async Task CheckForUpdates() { + Logger.Log("CheckForUpdates started."); foreach (ArmaMod mod in ModsCollection.ArmaMods) - { Task.Run(() => mod.UpdateInfos()); } + { + Logger.Log($" Checking mod {mod.WorkshopId} ({mod.Name})..."); + await Task.Run(() => mod.UpdateInfos()); + await Task.Delay(300); + } + Logger.Log("CheckForUpdates finished."); } public async Task UpdateSelectedMods() @@ -253,8 +259,117 @@ public async Task UpdateAll() MainWindow.Instance.NavigateToConsole(); var ans = await MainWindow.Instance.SteamUpdaterViewModel.RunModsUpdater(ModsCollection.ArmaMods); - if(ans == UpdateState.LoginFailed) + if(ans == UpdateState.LoginFailed) + DisplayMessage("Steam Login Failed"); + } + + public void PurgeAndReinstallMod(ArmaMod mod) + { + if (mod == null) return; + + Logger.Log($"PurgeAndReinstallMod: {mod.WorkshopId} ({mod.Name}) path={mod.Path}"); + try + { + if (Directory.Exists(mod.Path)) + { + Directory.Delete(mod.Path, true); + Logger.Log($" Deleted folder: {mod.Path}"); + } + else + Logger.Log($" Folder not found, skipping delete: {mod.Path}"); + } + catch (Exception ex) + { + Logger.Log($" ERROR deleting folder: {ex.Message}"); + DisplayMessage($"Could not delete folder for mod {mod.WorkshopId}"); + } + + mod.Status = ArmaModStatus.UpdateRequired; + mod.LocalLastUpdated = 0; + mod.Size = 0; + Properties.Settings.Default.Save(); + } + + public void PurgeAndReinstallSelectedMods() + { + var selectedMods = new List(ModsCollection.ArmaMods.Where(m => m.IsSelected && !m.IsLocal)); + foreach (var mod in selectedMods) + PurgeAndReinstallMod(mod); + } + + public async Task PurgeAndReinstallAll() + { + var answer = await DialogCoordinator.ShowInputAsync(this, "Are you sure you want to purge all mods?", "Write \"yes\" and press OK to delete all folders in the Mod Staging Directory and re-download everything."); + + if (string.IsNullOrEmpty(answer) || !answer.Equals("yes")) + return; + + Analytics.TrackEvent("Mods - Clicked PurgeAndReinstallAll", new Dictionary + { + {"Name", Properties.Settings.Default.steamUserName} + }); + + var stagingDir = Properties.Settings.Default.modStagingDirectory; + Logger.Log($"PurgeAndReinstallAll: staging dir={stagingDir}"); + if (Directory.Exists(stagingDir)) + { + foreach (var dir in Directory.GetDirectories(stagingDir)) + { + try + { + Directory.Delete(dir, true); + Logger.Log($" Deleted: {dir}"); + } + catch (Exception ex) + { + Logger.Log($" ERROR deleting {dir}: {ex.Message}"); + DisplayMessage($"Could not delete folder: {dir}"); + } + } + } + else + Logger.Log(" Staging dir does not exist, nothing deleted."); + + foreach (var mod in ModsCollection.ArmaMods.Where(m => !m.IsLocal).ToList()) + { + mod.Status = ArmaModStatus.UpdateRequired; + mod.LocalLastUpdated = 0; + mod.Size = 0; + Logger.Log($" Reset mod {mod.WorkshopId} ({mod.Name})"); + } + Properties.Settings.Default.Save(); + + Logger.Log("PurgeAndReinstallAll: launching UpdateAll..."); + MainWindow.Instance.NavigateToConsole(); + var ans = await MainWindow.Instance.SteamUpdaterViewModel.RunModsUpdater(ModsCollection.ArmaMods); + if (ans == UpdateState.LoginFailed) DisplayMessage("Steam Login Failed"); } + + public async Task PurgeUnusedMods() + { + var usedIds = Properties.Settings.Default.Profiles + .SelectMany(p => p.ProfileMods ?? Enumerable.Empty()) + .Select(m => m.Id) + .ToHashSet(); + + var unusedMods = ModsCollection.ArmaMods + .Where(m => !m.IsLocal && !usedIds.Contains(m.WorkshopId)) + .ToList(); + + if (unusedMods.Count == 0) + { + DisplayMessage("No unused mods found."); + return; + } + + var result = await DialogCoordinator.ShowInputAsync(this, + "Purge Unused Mods", + $"Found {unusedMods.Count} unused mod(s). Type \"yes\" to confirm deletion."); + if (result?.ToLower() != "yes") return; + + foreach (var mod in unusedMods) + DeleteMod(mod); + } } } diff --git a/FASTER/ViewModel/ProfileViewModel.cs b/FASTER/ViewModel/ProfileViewModel.cs index b9b6f71..c2d8b85 100644 --- a/FASTER/ViewModel/ProfileViewModel.cs +++ b/FASTER/ViewModel/ProfileViewModel.cs @@ -34,6 +34,7 @@ public ProfileViewModel(ServerProfile p) public ObservableCollection MissionDifficulties { get; } = new ObservableCollection { "Recruit", "Regular", "Veteran", "Custom" }; public ObservableCollection PerfPresets { get; } = new ObservableCollection(BasicCfgArrays.PerfPresets); public ObservableCollection TerrainGrids { get; } = new ObservableCollection(BasicCfgArrays.TerrainGrids); + public ObservableCollection Languages { get; } = new ObservableCollection(BasicCfgArrays.Languages); internal void DisplayMessage(string msg) { @@ -322,6 +323,54 @@ internal void LoadModsFromFile() } } + internal void SelectBePath() + { + var dialog = new CommonOpenFileDialog + { + Title = "Select the BattlEye directory", + IsFolderPicker = true, + AddToMostRecentlyUsedList = false, + AllowNonFileSystemItems = false, + EnsureFileExists = false, + EnsurePathExists = true, + EnsureReadOnly = false, + EnsureValidNames = true, + Multiselect = false, + ShowPlacesList = true + }; + + if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return; + + if (dialog.FileName != null) + { Profile.BePath = dialog.FileName; } + else + { MessageBox.Show("Please enter a valid BattlEye directory"); } + } + + internal void SelectKeysFolder() + { + var dialog = new CommonOpenFileDialog + { + Title = "Select the keys directory", + IsFolderPicker = true, + AddToMostRecentlyUsedList = false, + AllowNonFileSystemItems = false, + EnsureFileExists = false, + EnsurePathExists = true, + EnsureReadOnly = false, + EnsureValidNames = true, + Multiselect = false, + ShowPlacesList = true + }; + + if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return; + + if (dialog.FileName != null) + { Profile.KeysFolder = dialog.FileName; } + else + { MessageBox.Show("Please enter a valid keys directory"); } + } + internal void SelectServerFile() { var dialog = new CommonOpenFileDialog diff --git a/FASTER/Views/Mods.xaml b/FASTER/Views/Mods.xaml index ab18255..1f10728 100644 --- a/FASTER/Views/Mods.xaml +++ b/FASTER/Views/Mods.xaml @@ -51,6 +51,12 @@