diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI.Installer/unix-launcher.sh | 6 | ||||
-rw-r--r-- | src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs | 27 | ||||
-rw-r--r-- | src/SMAPI.Mods.SaveBackup/ModEntry.cs | 107 | ||||
-rw-r--r-- | src/SMAPI.Mods.SaveBackup/StardewModdingAPI.Mods.SaveBackup.csproj | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/Content/AssetDataForImage.cs | 6 | ||||
-rw-r--r-- | src/SMAPI/Framework/Input/SInputState.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/Models/SConfig.cs | 3 | ||||
-rw-r--r-- | src/SMAPI/IAssetDataForImage.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Program.cs | 18 | ||||
-rw-r--r-- | src/SMAPI/StardewModdingAPI.config.json | 10 |
10 files changed, 130 insertions, 55 deletions
diff --git a/src/SMAPI.Installer/unix-launcher.sh b/src/SMAPI.Installer/unix-launcher.sh index 6e796461..1e969c20 100644 --- a/src/SMAPI.Installer/unix-launcher.sh +++ b/src/SMAPI.Installer/unix-launcher.sh @@ -74,11 +74,11 @@ else elif $COMMAND xterm 2>/dev/null; then xterm -e "$LAUNCHER" elif $COMMAND xfce4-terminal 2>/dev/null; then - xfce4-terminal -e "$LAUNCHER" + xfce4-terminal -e "env TERM=xterm; $LAUNCHER" elif $COMMAND gnome-terminal 2>/dev/null; then - gnome-terminal -e "$LAUNCHER" + gnome-terminal -e "env TERM=xterm; $LAUNCHER" elif $COMMAND konsole 2>/dev/null; then - konsole -e "$LAUNCHER" + konsole -p Environment=TERM=xterm -e "$LAUNCHER" elif $COMMAND terminal 2>/dev/null; then terminal -e "$LAUNCHER" else diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs index ba2e671d..3fec8215 100644 --- a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs +++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs @@ -73,11 +73,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework continue; // ignore release zips - if (this.EqualsInvariant(file.Extension, ".zip")) - continue; - - // ignore Json.NET (bundled into SMAPI) - if (this.EqualsInvariant(file.Name, "Newtonsoft.Json.dll") || this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml")) + if (this.ShouldIgnore(file)) continue; // add file @@ -145,6 +141,27 @@ namespace StardewModdingAPI.ModBuildConfig.Framework /********* ** Private methods *********/ + /// <summary>Get whether a build output file should be ignored.</summary> + /// <param name="file">The file info.</param> + private bool ShouldIgnore(FileInfo file) + { + return + // release zips + this.EqualsInvariant(file.Extension, ".zip") + + // Json.NET (bundled into SMAPI) + || this.EqualsInvariant(file.Name, "Newtonsoft.Json.dll") + || this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml") + + // code analysis files + || file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.InvariantCultureIgnoreCase) + || file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.InvariantCultureIgnoreCase) + + // OS metadata files + || this.EqualsInvariant(file.Name, ".DS_Store") + || this.EqualsInvariant(file.Name, "Thumbs.db"); + } + /// <summary>Get a case-insensitive dictionary matching the given JSON.</summary> /// <param name="json">The JSON to parse.</param> private IDictionary<string, object> Parse(string json) diff --git a/src/SMAPI.Mods.SaveBackup/ModEntry.cs b/src/SMAPI.Mods.SaveBackup/ModEntry.cs index e9e62752..78578c3c 100644 --- a/src/SMAPI.Mods.SaveBackup/ModEntry.cs +++ b/src/SMAPI.Mods.SaveBackup/ModEntry.cs @@ -1,8 +1,9 @@ using System; -using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; +using System.Reflection; using StardewModdingAPI.Mods.SaveBackup.Framework; using StardewValley; @@ -27,25 +28,89 @@ namespace StardewModdingAPI.Mods.SaveBackup { try { - // read config ModConfig config = this.Helper.ReadConfig<ModConfig>(); // init backup folder - DirectoryInfo folder = new DirectoryInfo(Path.Combine(this.Helper.DirectoryPath, "backups")); - folder.Create(); + DirectoryInfo backupFolder = new DirectoryInfo(Path.Combine(this.Helper.DirectoryPath, "backups")); + backupFolder.Create(); // back up saves + this.CreateBackup(backupFolder); + this.PruneBackups(backupFolder, config.BackupsToKeep); + } + catch (Exception ex) + { + this.Monitor.Log($"Error backing up saves: {ex}"); + } + } + + + /********* + ** Private methods + *********/ + /// <summary>Back up the current saves.</summary> + /// <param name="backupFolder">The folder containing save backups.</param> + private void CreateBackup(DirectoryInfo backupFolder) + { + try + { + // get target path + FileInfo targetFile = new FileInfo(Path.Combine(backupFolder.FullName, this.FileName)); + if (targetFile.Exists) + targetFile.Delete(); //return; + + // create zip + // due to limitations with the bundled Mono on Mac, we can't reference System.IO.Compression. + this.Monitor.Log($"Adding {targetFile.Name}...", LogLevel.Trace); + switch (Constants.TargetPlatform) { - FileInfo file = new FileInfo(Path.Combine(folder.FullName, this.FileName)); - if (!file.Exists) - { - this.Monitor.Log($"Adding {file.Name}...", LogLevel.Trace); - ZipFile.CreateFromDirectory(Constants.SavesPath, file.FullName, CompressionLevel.Fastest, includeBaseDirectory: false); - } + case GamePlatform.Linux: + case GamePlatform.Windows: + { + Assembly coreAssembly = Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly."); + Assembly fsAssembly = Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly."); + Type compressionLevelType = coreAssembly.GetType("System.IO.Compression.CompressionLevel") ?? throw new InvalidOperationException("Can't load CompressionLevel type."); + Type zipFileType = fsAssembly.GetType("System.IO.Compression.ZipFile") ?? throw new InvalidOperationException("Can't load ZipFile type."); + MethodInfo createMethod = zipFileType.GetMethod("CreateFromDirectory", new[] { typeof(string), typeof(string), compressionLevelType, typeof(bool) }) ?? throw new InvalidOperationException("Can't load ZipFile.CreateFromDirectory method."); + createMethod.Invoke(null, new object[] { Constants.SavesPath, targetFile.FullName, CompressionLevel.Fastest, false }); + } + break; + + case GamePlatform.Mac: + { + DirectoryInfo saveFolder = new DirectoryInfo(Constants.SavesPath); + ProcessStartInfo startInfo = new ProcessStartInfo + { + FileName = "zip", + Arguments = $"-rq \"{targetFile.FullName}\" \"{saveFolder.Name}\" -x \"*.DS_Store\" -x \"__MACOSX\"", + WorkingDirectory = $"{Constants.SavesPath}/../", + CreateNoWindow = true + }; + new Process { StartInfo = startInfo }.Start(); + } + break; } + } + catch (Exception ex) + { + this.Monitor.Log("Couldn't back up save files (see log file for details).", LogLevel.Warn); + this.Monitor.Log(ex.ToString(), LogLevel.Trace); + } + } + + /// <summary>Remove old backups if we've exceeded the limit.</summary> + /// <param name="backupFolder">The folder containing save backups.</param> + /// <param name="backupsToKeep">The number of backups to keep.</param> + private void PruneBackups(DirectoryInfo backupFolder, int backupsToKeep) + { + try + { + var oldBackups = backupFolder + .GetFiles() + .OrderByDescending(p => p.CreationTimeUtc) + .Skip(backupsToKeep); - // prune old saves - foreach (FileInfo file in this.GetOldBackups(folder, config.BackupsToKeep)) + foreach (FileInfo file in oldBackups) { try { @@ -60,23 +125,9 @@ namespace StardewModdingAPI.Mods.SaveBackup } catch (Exception ex) { - this.Monitor.Log($"Error backing up saves: {ex}"); + this.Monitor.Log("Couldn't remove old backups (see log file for details).", LogLevel.Warn); + this.Monitor.Log(ex.ToString(), LogLevel.Trace); } } - - - /********* - ** Private methods - *********/ - /// <summary>Get backups ordered by creation date.</summary> - /// <param name="folder">The folder to search.</param> - /// <param name="skip">The number of backups to skip.</param> - private IEnumerable<FileInfo> GetOldBackups(DirectoryInfo folder, int skip) - { - return folder - .GetFiles() - .OrderByDescending(p => p.CreationTimeUtc) - .Skip(skip); - } } } diff --git a/src/SMAPI.Mods.SaveBackup/StardewModdingAPI.Mods.SaveBackup.csproj b/src/SMAPI.Mods.SaveBackup/StardewModdingAPI.Mods.SaveBackup.csproj index 89e92a8a..44fff536 100644 --- a/src/SMAPI.Mods.SaveBackup/StardewModdingAPI.Mods.SaveBackup.csproj +++ b/src/SMAPI.Mods.SaveBackup/StardewModdingAPI.Mods.SaveBackup.csproj @@ -31,8 +31,6 @@ </PropertyGroup> <ItemGroup> <Reference Include="System" /> - <Reference Include="System.IO.Compression" /> - <Reference Include="System.IO.Compression.FileSystem" /> </ItemGroup> <ItemGroup> <Compile Include="..\..\build\GlobalAssemblyInfo.cs"> diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 1eef2afb..5c7b87de 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -4,7 +4,7 @@ using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI.Framework.Content { - /// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary> + /// <summary>Encapsulates access and changes to image content being read from a data file.</summary> internal class AssetDataForImage : AssetData<Texture2D>, IAssetDataForImage { /********* @@ -29,6 +29,8 @@ namespace StardewModdingAPI.Framework.Content public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) { // get texture + if (source == null) + throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); Texture2D target = this.Data; // get areas @@ -36,8 +38,6 @@ namespace StardewModdingAPI.Framework.Content targetArea = targetArea ?? new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); // validate - if (source == null) - throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height) throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); if (targetArea.Value.X < 0 || targetArea.Value.Y < 0 || targetArea.Value.Right > target.Width || targetArea.Value.Bottom > target.Height) diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs index 5e8efa62..27e40ab4 100644 --- a/src/SMAPI/Framework/Input/SInputState.cs +++ b/src/SMAPI/Framework/Input/SInputState.cs @@ -160,7 +160,7 @@ namespace StardewModdingAPI.Framework.Input /// <summary>Whether input should be suppressed in the current context.</summary> private bool ShouldSuppressNow() { - return Game1.chatBox != null && !Game1.chatBox.isActive(); + return Game1.chatBox == null || !Game1.chatBox.isActive(); } /// <summary>Apply input suppression to the given input states.</summary> diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index e201e966..98614933 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -28,5 +28,8 @@ namespace StardewModdingAPI.Framework.Models /// <summary>The console color scheme to use.</summary> public MonitorColorScheme ColorScheme { get; set; } + + /// <summary>The mod IDs SMAPI should ignore when performing update checks or validating update keys.</summary> + public string[] SuppressUpdateChecks { get; set; } } } diff --git a/src/SMAPI/IAssetDataForImage.cs b/src/SMAPI/IAssetDataForImage.cs index 4584a20e..1109194f 100644 --- a/src/SMAPI/IAssetDataForImage.cs +++ b/src/SMAPI/IAssetDataForImage.cs @@ -1,10 +1,10 @@ -using System; +using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI { - /// <summary>Encapsulates access and changes to dictionary content being read from a data file.</summary> + /// <summary>Encapsulates access and changes to image content being read from a data file.</summary> public interface IAssetDataForImage : IAssetData<Texture2D> { /********* diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 340d2ddb..aeb9b9fb 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -94,14 +94,7 @@ namespace StardewModdingAPI new Regex(@"^loadPreferences\(\); begin", RegexOptions.Compiled | RegexOptions.CultureInvariant), new Regex(@"^savePreferences\(\); async=", RegexOptions.Compiled | RegexOptions.CultureInvariant), new Regex(@"^Multiplayer auth success$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^DebugOutput: added CLOUD", RegexOptions.Compiled | RegexOptions.CultureInvariant) - }; - - /// <summary>The mod IDs for which to not show missing update key warnings.</summary> - private readonly string[] AllowMissingUpdateKeys = - { - "SMAPI.ConsoleCommands", - "SMAPI.SaveBackup" + new Regex(@"^DebugOutput: (?:added CLOUD|dismount tile|Ping|playerPos)", RegexOptions.Compiled | RegexOptions.CultureInvariant) }; /// <summary>Encapsulates SMAPI's JSON file parsing.</summary> @@ -615,11 +608,15 @@ namespace StardewModdingAPI { try { + HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.InvariantCultureIgnoreCase); + // prepare update keys Dictionary<string, IModMetadata[]> modsByKey = ( from mod in mods - where mod.Manifest?.UpdateKeys != null + where + mod.Manifest?.UpdateKeys != null + && !suppressUpdateChecks.Contains(mod.Manifest.UniqueID) from key in mod.Manifest.UpdateKeys select new { key, mod } ) @@ -732,6 +729,7 @@ namespace StardewModdingAPI { this.Monitor.Log("Loading mods...", LogLevel.Trace); + HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.InvariantCultureIgnoreCase); IDictionary<IModMetadata, string[]> skippedMods = new Dictionary<IModMetadata, string[]>(); void TrackSkip(IModMetadata mod, string userReasonPhrase, string devReasonPhrase = null) => skippedMods[mod] = new[] { userReasonPhrase, devReasonPhrase }; @@ -789,7 +787,7 @@ namespace StardewModdingAPI : $" {metadata.DisplayName}...", LogLevel.Trace); // show warnings - if (metadata.HasManifest() && !metadata.HasUpdateKeys() && !this.AllowMissingUpdateKeys.Contains(metadata.Manifest.UniqueID)) + if (metadata.HasManifest() && !metadata.HasUpdateKeys() && !suppressUpdateChecks.Contains(metadata.Manifest.UniqueID)) metadata.SetWarning(ModWarning.NoUpdateKeys); // validate status diff --git a/src/SMAPI/StardewModdingAPI.config.json b/src/SMAPI/StardewModdingAPI.config.json index 6725dbbd..7aac6e4c 100644 --- a/src/SMAPI/StardewModdingAPI.config.json +++ b/src/SMAPI/StardewModdingAPI.config.json @@ -50,5 +50,13 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha * - LightBackground: use darker text colors that look better on a white or light background. * - DarkBackground: use lighter text colors that look better on a black or dark background. */ - "ColorScheme": "AutoDetect" + "ColorScheme": "AutoDetect", + + /** + * The mod IDs SMAPI should ignore when performing update checks or validating update keys. + */ + "SuppressUpdateChecks": [ + "SMAPI.ConsoleCommands", + "SMAPI.SaveBackup" + ] } |