From 756f18f3e7f7849f53add8fa1dcce99250acbc95 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Nov 2024 19:21:12 -0500 Subject: [PATCH 1/3] migrate to file-scoped namespaces --- .../Framework/ConsoleProgressBar.cs | 109 ++-- .../Framework/DefaultConsoleLogger.cs | 185 +++--- StardewXnbHack/Framework/PlatformContext.cs | 229 ++++--- StardewXnbHack/Framework/UnpackContext.cs | 31 +- .../Framework/Writers/BaseAssetWriter.cs | 103 ++- .../Framework/Writers/DataWriter.cs | 67 +- .../Framework/Writers/IAssetWriter.cs | 37 +- ...IgnoreDefaultOptionalPropertiesResolver.cs | 151 +++-- StardewXnbHack/Framework/Writers/MapWriter.cs | 161 +++-- .../Framework/Writers/SpriteFontWriter.cs | 143 +++-- .../Framework/Writers/TextureWriter.cs | 101 ++- .../Framework/Writers/XmlSourceWriter.cs | 51 +- StardewXnbHack/Program.cs | 593 +++++++++--------- .../ProgressHandling/IProgressLogger.cs | 49 +- .../ProgressHandling/IUnpackContext.cs | 25 +- .../ProgressHandling/ProgressStep.cs | 29 +- .../ProgressHandling/UnpackFailedReason.cs | 25 +- 17 files changed, 1036 insertions(+), 1053 deletions(-) diff --git a/StardewXnbHack/Framework/ConsoleProgressBar.cs b/StardewXnbHack/Framework/ConsoleProgressBar.cs index 9eda05d..b657d78 100644 --- a/StardewXnbHack/Framework/ConsoleProgressBar.cs +++ b/StardewXnbHack/Framework/ConsoleProgressBar.cs @@ -1,73 +1,72 @@ using System; -namespace StardewXnbHack.Framework +namespace StardewXnbHack.Framework; + +/// Manages a progress bar written to the console. +internal class ConsoleProgressBar { - /// Manages a progress bar written to the console. - internal class ConsoleProgressBar - { - /********* - ** Fields - *********/ - /// The total number of steps to perform. - private readonly int TotalSteps; + /********* + ** Fields + *********/ + /// The total number of steps to perform. + private readonly int TotalSteps; - /// The current step being performed. - private int CurrentStep; + /// The current step being performed. + private int CurrentStep; - /// The last line to which the progress bar was output, if any. - private int OutputLine = -1; + /// The last line to which the progress bar was output, if any. + private int OutputLine = -1; - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The total number of steps to perform. - public ConsoleProgressBar(int totalSteps) - { - this.TotalSteps = totalSteps; - } + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The total number of steps to perform. + public ConsoleProgressBar(int totalSteps) + { + this.TotalSteps = totalSteps; + } - /// Increment the current step. - public void Increment() - { - this.CurrentStep++; - } + /// Increment the current step. + public void Increment() + { + this.CurrentStep++; + } - /// Print a progress bar to the console. - /// The message to print. - /// Whether to remove the previously output progress bar. - public void Print(string message, bool removePrevious = true) - { - if (removePrevious) - this.Erase(); + /// Print a progress bar to the console. + /// The message to print. + /// Whether to remove the previously output progress bar. + public void Print(string message, bool removePrevious = true) + { + if (removePrevious) + this.Erase(); - int percentage = (int)((this.CurrentStep / (this.TotalSteps * 1m)) * 100); + int percentage = (int)((this.CurrentStep / (this.TotalSteps * 1m)) * 100); - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine($"[{"".PadRight(percentage / 10, '#')}{"".PadRight(10 - percentage / 10, ' ')} {percentage}%] {message}"); - Console.ResetColor(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"[{"".PadRight(percentage / 10, '#')}{"".PadRight(10 - percentage / 10, ' ')} {percentage}%] {message}"); + Console.ResetColor(); - this.OutputLine = Console.CursorTop - 1; - } + this.OutputLine = Console.CursorTop - 1; + } - /// Remove the last progress bar written to the console. - /// Derived from . - public void Erase() - { - if (this.OutputLine == -1) - return; + /// Remove the last progress bar written to the console. + /// Derived from . + public void Erase() + { + if (this.OutputLine == -1) + return; - bool isLastLine = this.OutputLine == Console.CursorTop - 1; - int currentLine = isLastLine - ? this.OutputLine - : Console.CursorTop; + bool isLastLine = this.OutputLine == Console.CursorTop - 1; + int currentLine = isLastLine + ? this.OutputLine + : Console.CursorTop; - Console.SetCursorPosition(0, this.OutputLine); - Console.Write(new string(' ', Console.BufferWidth)); - Console.SetCursorPosition(0, currentLine); + Console.SetCursorPosition(0, this.OutputLine); + Console.Write(new string(' ', Console.BufferWidth)); + Console.SetCursorPosition(0, currentLine); - this.OutputLine = -1; - } + this.OutputLine = -1; } } diff --git a/StardewXnbHack/Framework/DefaultConsoleLogger.cs b/StardewXnbHack/Framework/DefaultConsoleLogger.cs index 23b5f2c..3fe73bf 100644 --- a/StardewXnbHack/Framework/DefaultConsoleLogger.cs +++ b/StardewXnbHack/Framework/DefaultConsoleLogger.cs @@ -2,101 +2,100 @@ using System.Linq; using StardewXnbHack.ProgressHandling; -namespace StardewXnbHack.Framework +namespace StardewXnbHack.Framework; + +/// Report updates to the console while the unpacker is running. +internal class DefaultConsoleLogger : IProgressLogger { - /// Report updates to the console while the unpacker is running. - internal class DefaultConsoleLogger : IProgressLogger + /********* + ** Fields + *********/ + /// The context info for the current unpack run. + private readonly IUnpackContext Context; + + /// Whether to show a 'press any key to exit' prompt on end. + private readonly bool ShowPressAnyKeyToExit; + + /// The current progress bar written to the console. + private ConsoleProgressBar ProgressBar; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The context info for the current unpack run. + /// Whether to show a 'press any key to exit' prompt on end. + public DefaultConsoleLogger(IUnpackContext context, bool showPressAnyKeyToExit) + { + this.Context = context; + this.ShowPressAnyKeyToExit = showPressAnyKeyToExit; + } + + /// + public void OnFatalError(string error) + { + this.PrintColor(error, ConsoleColor.Red); + } + + /// + public void OnStepChanged(ProgressStep step, string message) { - /********* - ** Fields - *********/ - /// The context info for the current unpack run. - private readonly IUnpackContext Context; - - /// Whether to show a 'press any key to exit' prompt on end. - private readonly bool ShowPressAnyKeyToExit; - - /// The current progress bar written to the console. - private ConsoleProgressBar ProgressBar; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The context info for the current unpack run. - /// Whether to show a 'press any key to exit' prompt on end. - public DefaultConsoleLogger(IUnpackContext context, bool showPressAnyKeyToExit) - { - this.Context = context; - this.ShowPressAnyKeyToExit = showPressAnyKeyToExit; - } - - /// - public void OnFatalError(string error) - { - this.PrintColor(error, ConsoleColor.Red); - } - - /// - public void OnStepChanged(ProgressStep step, string message) - { - this.ProgressBar?.Erase(); - - if (step == ProgressStep.Done) - Console.WriteLine(); - - Console.WriteLine(message); - } - - /// - public void OnFileUnpacking(string relativePath) - { - if (this.ProgressBar == null) - this.ProgressBar = new ConsoleProgressBar(this.Context.Files.Count()); - - this.ProgressBar.Increment(); - this.ProgressBar.Print(relativePath); - } - - /// - public void OnFileUnpackFailed(string relativePath, UnpackFailedReason errorCode, string errorMessage) - { - ConsoleColor color = errorCode == UnpackFailedReason.UnsupportedFileType - ? ConsoleColor.DarkYellow - : ConsoleColor.Red; - - this.ProgressBar.Erase(); - this.PrintColor($"{relativePath} => {errorMessage}", color); - } - - /// - public void OnEnded() - { - if (this.ShowPressAnyKeyToExit) - DefaultConsoleLogger.PressAnyKeyToExit(); - } - - /// Show a 'press any key to exit' message and wait for a key press. - public static void PressAnyKeyToExit() - { + this.ProgressBar?.Erase(); + + if (step == ProgressStep.Done) Console.WriteLine(); - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - } - - - /********* - ** Private methods - *********/ - /// Print a message to the console with a foreground color. - /// The message to print. - /// The foreground color to use. - private void PrintColor(string message, ConsoleColor color) - { - Console.ForegroundColor = color; - Console.WriteLine(message); - Console.ResetColor(); - } + + Console.WriteLine(message); + } + + /// + public void OnFileUnpacking(string relativePath) + { + if (this.ProgressBar == null) + this.ProgressBar = new ConsoleProgressBar(this.Context.Files.Count()); + + this.ProgressBar.Increment(); + this.ProgressBar.Print(relativePath); + } + + /// + public void OnFileUnpackFailed(string relativePath, UnpackFailedReason errorCode, string errorMessage) + { + ConsoleColor color = errorCode == UnpackFailedReason.UnsupportedFileType + ? ConsoleColor.DarkYellow + : ConsoleColor.Red; + + this.ProgressBar.Erase(); + this.PrintColor($"{relativePath} => {errorMessage}", color); + } + + /// + public void OnEnded() + { + if (this.ShowPressAnyKeyToExit) + DefaultConsoleLogger.PressAnyKeyToExit(); + } + + /// Show a 'press any key to exit' message and wait for a key press. + public static void PressAnyKeyToExit() + { + Console.WriteLine(); + Console.WriteLine("Press any key to exit."); + Console.ReadKey(); + } + + + /********* + ** Private methods + *********/ + /// Print a message to the console with a foreground color. + /// The message to print. + /// The foreground color to use. + private void PrintColor(string message, ConsoleColor color) + { + Console.ForegroundColor = color; + Console.WriteLine(message); + Console.ResetColor(); } } diff --git a/StardewXnbHack/Framework/PlatformContext.cs b/StardewXnbHack/Framework/PlatformContext.cs index 7b9bb6c..4c17004 100644 --- a/StardewXnbHack/Framework/PlatformContext.cs +++ b/StardewXnbHack/Framework/PlatformContext.cs @@ -5,142 +5,141 @@ using StardewModdingAPI.Toolkit; using StardewModdingAPI.Toolkit.Utilities; -namespace StardewXnbHack.Framework +namespace StardewXnbHack.Framework; + +/// Provides platform-specific information. +internal class PlatformContext { - /// Provides platform-specific information. - internal class PlatformContext + /********* + ** Accessors + *********/ + /// The current platform. + public Platform Platform { get; } = EnvironmentUtility.DetectPlatform(); + + + /********* + ** Public methods + *********/ + /// Get whether any of the listed platforms is the current one. + /// The platforms to match. + public bool Is(params Platform[] platforms) { - /********* - ** Accessors - *********/ - /// The current platform. - public Platform Platform { get; } = EnvironmentUtility.DetectPlatform(); - - - /********* - ** Public methods - *********/ - /// Get whether any of the listed platforms is the current one. - /// The platforms to match. - public bool Is(params Platform[] platforms) - { - return platforms.Contains(this.Platform); - } + return platforms.Contains(this.Platform); + } - /// Get the absolute paths to the game and content folders, if found. - /// The game path specified by the user, if any. - /// The absolute path to the game folder, if found. - /// The absolute path to the content folder, if found. - /// Returns whether both the game and content folders were found. - public bool TryDetectGamePaths(string specifiedPath, out string gamePath, out string contentPath) + /// Get the absolute paths to the game and content folders, if found. + /// The game path specified by the user, if any. + /// The absolute path to the game folder, if found. + /// The absolute path to the content folder, if found. + /// Returns whether both the game and content folders were found. + public bool TryDetectGamePaths(string specifiedPath, out string gamePath, out string contentPath) + { + gamePath = null; + contentPath = null; + + // check possible game paths + foreach (string candidate in this.GetCandidateGamePaths(specifiedPath)) { - gamePath = null; - contentPath = null; + // detect paths + string curGamePath = this.TryGamePath(candidate); + string curContentPath = this.FindContentPath(curGamePath); - // check possible game paths - foreach (string candidate in this.GetCandidateGamePaths(specifiedPath)) + // valid game install found + if (curGamePath != null && curContentPath != null) { - // detect paths - string curGamePath = this.TryGamePath(candidate); - string curContentPath = this.FindContentPath(curGamePath); - - // valid game install found - if (curGamePath != null && curContentPath != null) - { - gamePath = curGamePath; - contentPath = curContentPath; - return true; - } - - // if game folder exists without a content folder, track the first found game path (i.e. the highest-priority one) - gamePath ??= curGamePath; + gamePath = curGamePath; + contentPath = curContentPath; + return true; } - return false; + // if game folder exists without a content folder, track the first found game path (i.e. the highest-priority one) + gamePath ??= curGamePath; } + return false; + } - /********* - ** Private methods - *********/ - /// Get the possible game paths. - /// The game path specified by the user, if any. - private IEnumerable GetCandidateGamePaths(string specifiedPath = null) - { - // specified path - if (!string.IsNullOrWhiteSpace(specifiedPath)) - yield return specifiedPath; - // current working directory - yield return AppDomain.CurrentDomain.BaseDirectory; + /********* + ** Private methods + *********/ + /// Get the possible game paths. + /// The game path specified by the user, if any. + private IEnumerable GetCandidateGamePaths(string specifiedPath = null) + { + // specified path + if (!string.IsNullOrWhiteSpace(specifiedPath)) + yield return specifiedPath; - // detected game path - string detectedPath = new ModToolkit().GetGameFolders().FirstOrDefault()?.FullName; - if (detectedPath != null) - yield return detectedPath; - } + // current working directory + yield return AppDomain.CurrentDomain.BaseDirectory; - /// Get the absolute path to the game folder, if it's valid. - /// The path to check for a game install. - private string TryGamePath(string path) - { - // game path exists - if (path == null) - return null; - DirectoryInfo gameDir = new DirectoryInfo(path); - if (!gameDir.Exists) - return null; - - // has game files - bool hasGameDll = File.Exists(Path.Combine(gameDir.FullName, "Stardew Valley.dll")); - if (!hasGameDll) - return null; - - // isn't the build folder when compiled directly - bool isCompileFolder = File.Exists(Path.Combine(gameDir.FullName, "StardewXnbHack.exe.config")); - if (isCompileFolder) - return null; - - return gameDir.FullName; - } + // detected game path + string detectedPath = new ModToolkit().GetGameFolders().FirstOrDefault()?.FullName; + if (detectedPath != null) + yield return detectedPath; + } - /// Get the absolute path to the content folder for a given game, if found. - /// The absolute path to the game folder. - private string FindContentPath(string gamePath) - { - if (gamePath == null) - return null; + /// Get the absolute path to the game folder, if it's valid. + /// The path to check for a game install. + private string TryGamePath(string path) + { + // game path exists + if (path == null) + return null; + DirectoryInfo gameDir = new DirectoryInfo(path); + if (!gameDir.Exists) + return null; - foreach (string relativePath in this.GetPossibleRelativeContentPaths()) - { - DirectoryInfo folder = new DirectoryInfo(Path.Combine(gamePath, relativePath)); - if (folder.Exists) - return folder.FullName; - } + // has game files + bool hasGameDll = File.Exists(Path.Combine(gameDir.FullName, "Stardew Valley.dll")); + if (!hasGameDll) + return null; + // isn't the build folder when compiled directly + bool isCompileFolder = File.Exists(Path.Combine(gameDir.FullName, "StardewXnbHack.exe.config")); + if (isCompileFolder) + return null; + + return gameDir.FullName; + } + + /// Get the absolute path to the content folder for a given game, if found. + /// The absolute path to the game folder. + private string FindContentPath(string gamePath) + { + if (gamePath == null) return null; - } - /// Get the possible relative paths for the current platform. - private IEnumerable GetPossibleRelativeContentPaths() + foreach (string relativePath in this.GetPossibleRelativeContentPaths()) { - // under game folder on most platforms - if (this.Platform != Platform.Mac) - yield return "Content"; + DirectoryInfo folder = new DirectoryInfo(Path.Combine(gamePath, relativePath)); + if (folder.Exists) + return folder.FullName; + } - // macOS - else - { - // Steam paths - // - game path: StardewValley/Contents/MacOS - // - content: StardewValley/Contents/Resources/Content - yield return "../Resources/Content"; - - // GOG paths - // - game path: Stardew Valley.app/Contents/MacOS - // - content: Stardew Valley.app/Resources/Content - yield return "../../Resources/Content"; - } + return null; + } + + /// Get the possible relative paths for the current platform. + private IEnumerable GetPossibleRelativeContentPaths() + { + // under game folder on most platforms + if (this.Platform != Platform.Mac) + yield return "Content"; + + // macOS + else + { + // Steam paths + // - game path: StardewValley/Contents/MacOS + // - content: StardewValley/Contents/Resources/Content + yield return "../Resources/Content"; + + // GOG paths + // - game path: Stardew Valley.app/Contents/MacOS + // - content: Stardew Valley.app/Resources/Content + yield return "../../Resources/Content"; } } } diff --git a/StardewXnbHack/Framework/UnpackContext.cs b/StardewXnbHack/Framework/UnpackContext.cs index 75fce3f..72c95f4 100644 --- a/StardewXnbHack/Framework/UnpackContext.cs +++ b/StardewXnbHack/Framework/UnpackContext.cs @@ -2,24 +2,23 @@ using System.IO; using StardewXnbHack.ProgressHandling; -namespace StardewXnbHack.Framework +namespace StardewXnbHack.Framework; + +/// The context info for the current unpack run. +internal class UnpackContext : IUnpackContext { - /// The context info for the current unpack run. - internal class UnpackContext : IUnpackContext - { - /********* - ** Accessors - *********/ - /// - public string GamePath { get; set; } + /********* + ** Accessors + *********/ + /// + public string GamePath { get; set; } - /// - public string ContentPath { get; set; } + /// + public string ContentPath { get; set; } - /// - public string ExportPath { get; set; } + /// + public string ExportPath { get; set; } - /// - public IEnumerable Files { get; set; } - } + /// + public IEnumerable Files { get; set; } } diff --git a/StardewXnbHack/Framework/Writers/BaseAssetWriter.cs b/StardewXnbHack/Framework/Writers/BaseAssetWriter.cs index 961dae6..f78e905 100644 --- a/StardewXnbHack/Framework/Writers/BaseAssetWriter.cs +++ b/StardewXnbHack/Framework/Writers/BaseAssetWriter.cs @@ -5,68 +5,67 @@ using StardewModdingAPI.Toolkit.Serialization; using StardewModdingAPI.Toolkit.Utilities; -namespace StardewXnbHack.Framework.Writers +namespace StardewXnbHack.Framework.Writers; + +/// The base class for an asset writer. +internal abstract class BaseAssetWriter : IAssetWriter { - /// The base class for an asset writer. - internal abstract class BaseAssetWriter : IAssetWriter - { - /********* - ** Private methods - *********/ - /// The settings to use when serializing JSON. - private readonly Lazy JsonSettings; + /********* + ** Private methods + *********/ + /// The settings to use when serializing JSON. + private readonly Lazy JsonSettings; - /********* - ** Public methods - *********/ - /// Whether the writer can handle a given asset. - /// The asset value. - public abstract bool CanWrite(object asset); + /********* + ** Public methods + *********/ + /// Whether the writer can handle a given asset. + /// The asset value. + public abstract bool CanWrite(object asset); - /// Write an asset instance to disk. - /// The asset value. - /// The absolute path to the export file, without the file extension. - /// The relative path within the content folder. - /// The operating system running the unpacker. - /// An error phrase indicating why writing to disk failed (if applicable). - /// Returns whether writing to disk completed successfully. - public abstract bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error); + /// Write an asset instance to disk. + /// The asset value. + /// The absolute path to the export file, without the file extension. + /// The relative path within the content folder. + /// The operating system running the unpacker. + /// An error phrase indicating why writing to disk failed (if applicable). + /// Returns whether writing to disk completed successfully. + public abstract bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error); - /********* - ** Protected methods - *********/ - /// Construct an instance. - /// Whether to ignore members marked which match the default value. - protected BaseAssetWriter(bool omitDefaultFields = false) - { - this.JsonSettings = new(() => BaseAssetWriter.GetJsonSerializerSettings(omitDefaultFields)); - } + /********* + ** Protected methods + *********/ + /// Construct an instance. + /// Whether to ignore members marked which match the default value. + protected BaseAssetWriter(bool omitDefaultFields = false) + { + this.JsonSettings = new(() => BaseAssetWriter.GetJsonSerializerSettings(omitDefaultFields)); + } - /// Get a text representation for the given asset. - /// The asset to serialize. - protected string FormatData(object asset) - { - return JsonConvert.SerializeObject(asset, this.JsonSettings.Value); - } + /// Get a text representation for the given asset. + /// The asset to serialize. + protected string FormatData(object asset) + { + return JsonConvert.SerializeObject(asset, this.JsonSettings.Value); + } - /// Get the recommended file extension for a data file formatted with . - protected string GetDataExtension() - { - return "json"; - } + /// Get the recommended file extension for a data file formatted with . + protected string GetDataExtension() + { + return "json"; + } - /// Get the serializer settings to apply when writing JSON. - /// Whether to ignore members marked which match the default value. - private static JsonSerializerSettings GetJsonSerializerSettings(bool omitDefaultFields = false) - { - JsonHelper jsonHelper = new(); - JsonSerializerSettings settings = jsonHelper.JsonSettings.DeepClone(); + /// Get the serializer settings to apply when writing JSON. + /// Whether to ignore members marked which match the default value. + private static JsonSerializerSettings GetJsonSerializerSettings(bool omitDefaultFields = false) + { + JsonHelper jsonHelper = new(); + JsonSerializerSettings settings = jsonHelper.JsonSettings.DeepClone(); - settings.ContractResolver = new IgnoreDefaultOptionalPropertiesResolver(omitDefaultFields); + settings.ContractResolver = new IgnoreDefaultOptionalPropertiesResolver(omitDefaultFields); - return settings; - } + return settings; } } diff --git a/StardewXnbHack/Framework/Writers/DataWriter.cs b/StardewXnbHack/Framework/Writers/DataWriter.cs index 681d92c..187b42f 100644 --- a/StardewXnbHack/Framework/Writers/DataWriter.cs +++ b/StardewXnbHack/Framework/Writers/DataWriter.cs @@ -3,44 +3,43 @@ using System.IO; using StardewModdingAPI.Toolkit.Utilities; -namespace StardewXnbHack.Framework.Writers +namespace StardewXnbHack.Framework.Writers; + +/// Writes and assets to disk. +internal class DataWriter : BaseAssetWriter { - /// Writes and assets to disk. - internal class DataWriter : BaseAssetWriter - { - /********* - ** Public methods - *********/ - /// - public DataWriter(bool omitDefaultFields) - : base(omitDefaultFields) { } + /********* + ** Public methods + *********/ + /// + public DataWriter(bool omitDefaultFields) + : base(omitDefaultFields) { } - /// Whether the writer can handle a given asset. - /// The asset value. - public override bool CanWrite(object asset) - { - Type type = asset.GetType(); - type = type.IsGenericType ? type.GetGenericTypeDefinition() : type; + /// Whether the writer can handle a given asset. + /// The asset value. + public override bool CanWrite(object asset) + { + Type type = asset.GetType(); + type = type.IsGenericType ? type.GetGenericTypeDefinition() : type; - return - type == typeof(Dictionary<,>) - || type == typeof(List<>) - || type.FullName?.StartsWith("StardewValley.GameData.") == true; - } + return + type == typeof(Dictionary<,>) + || type == typeof(List<>) + || type.FullName?.StartsWith("StardewValley.GameData.") == true; + } - /// Write an asset instance to disk. - /// The asset value. - /// The absolute path to the export file, without the file extension. - /// The relative path within the content folder. - /// The operating system running the unpacker. - /// An error phrase indicating why writing to disk failed (if applicable). - /// Returns whether writing to disk completed successfully. - public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) - { - File.WriteAllText($"{toPathWithoutExtension}.{this.GetDataExtension()}", this.FormatData(asset)); + /// Write an asset instance to disk. + /// The asset value. + /// The absolute path to the export file, without the file extension. + /// The relative path within the content folder. + /// The operating system running the unpacker. + /// An error phrase indicating why writing to disk failed (if applicable). + /// Returns whether writing to disk completed successfully. + public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) + { + File.WriteAllText($"{toPathWithoutExtension}.{this.GetDataExtension()}", this.FormatData(asset)); - error = null; - return true; - } + error = null; + return true; } } diff --git a/StardewXnbHack/Framework/Writers/IAssetWriter.cs b/StardewXnbHack/Framework/Writers/IAssetWriter.cs index 168b05b..4d2c5e8 100644 --- a/StardewXnbHack/Framework/Writers/IAssetWriter.cs +++ b/StardewXnbHack/Framework/Writers/IAssetWriter.cs @@ -1,24 +1,23 @@ using StardewModdingAPI.Toolkit.Utilities; -namespace StardewXnbHack.Framework.Writers +namespace StardewXnbHack.Framework.Writers; + +/// Writes assets to disk. +internal interface IAssetWriter { - /// Writes assets assets to disk. - internal interface IAssetWriter - { - /********* - ** Methods - *********/ - /// Whether the writer can handle a given asset. - /// The asset value. - bool CanWrite(object asset); + /********* + ** Methods + *********/ + /// Whether the writer can handle a given asset. + /// The asset value. + bool CanWrite(object asset); - /// Write an asset instance to disk. - /// The asset value. - /// The absolute path to the export file, without the file extension. - /// The relative path within the content folder. - /// The operating system running the unpacker. - /// An error phrase indicating why writing to disk failed (if applicable). - /// Returns whether writing to disk completed successfully. - bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error); - } + /// Write an asset instance to disk. + /// The asset value. + /// The absolute path to the export file, without the file extension. + /// The relative path within the content folder. + /// The operating system running the unpacker. + /// An error phrase indicating why writing to disk failed (if applicable). + /// Returns whether writing to disk completed successfully. + bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error); } diff --git a/StardewXnbHack/Framework/Writers/IgnoreDefaultOptionalPropertiesResolver.cs b/StardewXnbHack/Framework/Writers/IgnoreDefaultOptionalPropertiesResolver.cs index b74cf3b..964d1c9 100644 --- a/StardewXnbHack/Framework/Writers/IgnoreDefaultOptionalPropertiesResolver.cs +++ b/StardewXnbHack/Framework/Writers/IgnoreDefaultOptionalPropertiesResolver.cs @@ -7,99 +7,98 @@ using Newtonsoft.Json.Serialization; using Sickhead.Engine.Util; -namespace StardewXnbHack.Framework.Writers +namespace StardewXnbHack.Framework.Writers; + +/// A Json.NET contract resolver which ignores properties marked with , or (optionally) marked with the default value. +internal class IgnoreDefaultOptionalPropertiesResolver : DefaultContractResolver { - /// A Json.NET contract resolver which ignores properties marked with , or (optionally) marked with the default value. - internal class IgnoreDefaultOptionalPropertiesResolver : DefaultContractResolver - { - /********* - ** Fields - *********/ - /// Whether to ignore members marked which match the default value. - private readonly bool OmitDefaultValues; + /********* + ** Fields + *********/ + /// Whether to ignore members marked which match the default value. + private readonly bool OmitDefaultValues; - /// The default values for fields and properties marked . - private readonly Dictionary> DefaultValues = new(); + /// The default values for fields and properties marked . + private readonly Dictionary> DefaultValues = new(); - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Whether to ignore members marked which match the default value. - public IgnoreDefaultOptionalPropertiesResolver(bool omitDefaultValues) - { - this.OmitDefaultValues = omitDefaultValues; - } + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Whether to ignore members marked which match the default value. + public IgnoreDefaultOptionalPropertiesResolver(bool omitDefaultValues) + { + this.OmitDefaultValues = omitDefaultValues; + } - /********* - ** Protected methods - *********/ - /// - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - JsonProperty property = base.CreateProperty(member, memberSerialization); + /********* + ** Protected methods + *********/ + /// + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + JsonProperty property = base.CreateProperty(member, memberSerialization); - // property marked ignore - if (member.GetCustomAttribute() != null) - property.ShouldSerialize = _ => false; + // property marked ignore + if (member.GetCustomAttribute() != null) + property.ShouldSerialize = _ => false; - // property marked optional which matches default value - else if (this.OmitDefaultValues) + // property marked optional which matches default value + else if (this.OmitDefaultValues) + { + Dictionary? optionalMembers = this.GetDefaultValues(member.DeclaringType); + if (optionalMembers != null && optionalMembers.TryGetValue(member.Name, out object defaultValue)) { - Dictionary? optionalMembers = this.GetDefaultValues(member.DeclaringType); - if (optionalMembers != null && optionalMembers.TryGetValue(member.Name, out object defaultValue)) + property.ShouldSerialize = instance => { - property.ShouldSerialize = instance => - { - object value = member.GetValue(instance); - return !defaultValue?.Equals(value) ?? value is not null; - }; - } + object value = member.GetValue(instance); + return !defaultValue?.Equals(value) ?? value is not null; + }; } - - return property; } - /// The default values for a type's fields and properties marked , if any. - /// The type whose fields and properties to get default values for. - /// Returns a dictionary of default values by member name if any were found, else null. - private Dictionary? GetDefaultValues(Type type) - { - // skip invalid - if (!type.IsClass || type.FullName is null || type.Namespace?.StartsWith("StardewValley") != true) - return null; + return property; + } - // skip if already cached - if (this.DefaultValues.TryGetValue(type.FullName, out Dictionary defaults)) - return defaults; + /// The default values for a type's fields and properties marked , if any. + /// The type whose fields and properties to get default values for. + /// Returns a dictionary of default values by member name if any were found, else null. + private Dictionary? GetDefaultValues(Type type) + { + // skip invalid + if (!type.IsClass || type.FullName is null || type.Namespace?.StartsWith("StardewValley") != true) + return null; - // get members - MemberInfo[] optionalMembers = - (type.GetFields().OfType()) - .Concat(type.GetProperties()) - .Where(member => member.GetCustomAttribute()?.Optional is true) - .ToArray(); - if (optionalMembers.Length == 0) - return this.DefaultValues[type.FullName] = null; + // skip if already cached + if (this.DefaultValues.TryGetValue(type.FullName, out Dictionary defaults)) + return defaults; - // get default instance - object defaultInstance; - try - { - defaultInstance = Activator.CreateInstance(type); - } - catch - { - return this.DefaultValues[type.FullName] = null; - } + // get members + MemberInfo[] optionalMembers = + (type.GetFields().OfType()) + .Concat(type.GetProperties()) + .Where(member => member.GetCustomAttribute()?.Optional is true) + .ToArray(); + if (optionalMembers.Length == 0) + return this.DefaultValues[type.FullName] = null; - // get default values - defaults = new Dictionary(); - foreach (MemberInfo member in optionalMembers) - defaults[member.Name] = member.GetValue(defaultInstance); - return this.DefaultValues[type.FullName] = defaults; + // get default instance + object defaultInstance; + try + { + defaultInstance = Activator.CreateInstance(type); } + catch + { + return this.DefaultValues[type.FullName] = null; + } + + // get default values + defaults = new Dictionary(); + foreach (MemberInfo member in optionalMembers) + defaults[member.Name] = member.GetValue(defaultInstance); + return this.DefaultValues[type.FullName] = defaults; } } diff --git a/StardewXnbHack/Framework/Writers/MapWriter.cs b/StardewXnbHack/Framework/Writers/MapWriter.cs index 1238df9..3ac78f2 100644 --- a/StardewXnbHack/Framework/Writers/MapWriter.cs +++ b/StardewXnbHack/Framework/Writers/MapWriter.cs @@ -10,102 +10,101 @@ using xTile.Layers; using xTile.Tiles; -namespace StardewXnbHack.Framework.Writers +namespace StardewXnbHack.Framework.Writers; + +/// Writes assets to disk. +internal class MapWriter : BaseAssetWriter { - /// Writes assets to disk. - internal class MapWriter : BaseAssetWriter + /********* + ** Fields + *********/ + /// The actual size of a tile in the tilesheet. + const int TileSize = Game1.tileSize / Game1.pixelZoom; + + /// The underlying map format handler. + private readonly TMXFormat Format; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public MapWriter() { - /********* - ** Fields - *********/ - /// The actual size of a tile in the tilesheet. - const int TileSize = Game1.tileSize / Game1.pixelZoom; + // init TMX support + this.Format = new TMXFormat(Game1.tileSize / Game1.pixelZoom, Game1.tileSize / Game1.pixelZoom, Game1.pixelZoom, Game1.pixelZoom); + } - /// The underlying map format handler. - private readonly TMXFormat Format; + /// Whether the writer can handle a given asset. + /// The asset value. + public override bool CanWrite(object asset) + { + return asset is Map; + } + /// Write an asset instance to disk. + /// The asset value. + /// The absolute path to the export file, without the file extension. + /// The relative path within the content folder. + /// The operating system running the unpacker. + /// An error phrase indicating why writing to disk failed (if applicable). + /// Returns whether writing to disk completed successfully. + public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) + { + Map map = (Map)asset; - /********* - ** Public methods - *********/ - /// Construct an instance. - public MapWriter() + // fix tile sizes (game overrides them in-memory) + IDictionary tileSizes = new Dictionary(); + foreach (var layer in map.Layers) { - // init TMX support - this.Format = new TMXFormat(Game1.tileSize / Game1.pixelZoom, Game1.tileSize / Game1.pixelZoom, Game1.pixelZoom, Game1.pixelZoom); + tileSizes[layer] = layer.TileSize; + layer.TileSize = new Size(MapWriter.TileSize, MapWriter.TileSize); } - /// Whether the writer can handle a given asset. - /// The asset value. - public override bool CanWrite(object asset) + // fix image sources (game overrides them in-memory) + IDictionary imageSources = new Dictionary(); + foreach (var sheet in map.TileSheets) { - return asset is Map; + imageSources[sheet] = sheet.ImageSource; + sheet.ImageSource = this.GetOriginalImageSource(relativePath, sheet.ImageSource); } - /// Write an asset instance to disk. - /// The asset value. - /// The absolute path to the export file, without the file extension. - /// The relative path within the content folder. - /// The operating system running the unpacker. - /// An error phrase indicating why writing to disk failed (if applicable). - /// Returns whether writing to disk completed successfully. - public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) + // save file + using (Stream stream = new MemoryStream()) { - Map map = (Map)asset; - - // fix tile sizes (game overrides them in-memory) - IDictionary tileSizes = new Dictionary(); - foreach (var layer in map.Layers) - { - tileSizes[layer] = layer.TileSize; - layer.TileSize = new Size(MapWriter.TileSize, MapWriter.TileSize); - } - - // fix image sources (game overrides them in-memory) - IDictionary imageSources = new Dictionary(); - foreach (var sheet in map.TileSheets) - { - imageSources[sheet] = sheet.ImageSource; - sheet.ImageSource = this.GetOriginalImageSource(relativePath, sheet.ImageSource); - } - - // save file - using (Stream stream = new MemoryStream()) - { - // serialize to stream - this.Format.Store(map, stream, DataEncodingType.CSV); - - // workaround: TMXTile doesn't indent the XML in newer .NET versions - stream.Position = 0; - var doc = XDocument.Load(stream); - File.WriteAllText($"{toPathWithoutExtension}.tmx", "\n" + doc.ToString()); - } - - // undo changes - foreach (var layer in map.Layers) - layer.TileSize = tileSizes[layer]; - foreach (var sheet in map.TileSheets) - sheet.ImageSource = imageSources[sheet]; - - error = null; - return true; + // serialize to stream + this.Format.Store(map, stream, DataEncodingType.CSV); + + // workaround: TMXTile doesn't indent the XML in newer .NET versions + stream.Position = 0; + var doc = XDocument.Load(stream); + File.WriteAllText($"{toPathWithoutExtension}.tmx", "\n" + doc.ToString()); } + // undo changes + foreach (var layer in map.Layers) + layer.TileSize = tileSizes[layer]; + foreach (var sheet in map.TileSheets) + sheet.ImageSource = imageSources[sheet]; - /********* - ** Public methods - *********/ - /// Get the image source for a map tilesheet without the game's automatic path changes. - /// The relative path to the map file within the content folder. - /// The tilesheet image source. - private string GetOriginalImageSource(string relativeMapPath, string imageSource) - { - string mapDirPath = PathUtilities.NormalizePath(Path.GetDirectoryName(relativeMapPath)); - string normalizedImageSource = PathUtilities.NormalizePath(imageSource); + error = null; + return true; + } - return normalizedImageSource.StartsWith($"{mapDirPath}{PathUtilities.PreferredPathSeparator}", StringComparison.OrdinalIgnoreCase) - ? imageSource.Substring(mapDirPath.Length + 1) - : imageSource; - } + + /********* + ** Public methods + *********/ + /// Get the image source for a map tilesheet without the game's automatic path changes. + /// The relative path to the map file within the content folder. + /// The tilesheet image source. + private string GetOriginalImageSource(string relativeMapPath, string imageSource) + { + string mapDirPath = PathUtilities.NormalizePath(Path.GetDirectoryName(relativeMapPath)); + string normalizedImageSource = PathUtilities.NormalizePath(imageSource); + + return normalizedImageSource.StartsWith($"{mapDirPath}{PathUtilities.PreferredPathSeparator}", StringComparison.OrdinalIgnoreCase) + ? imageSource.Substring(mapDirPath.Length + 1) + : imageSource; } } diff --git a/StardewXnbHack/Framework/Writers/SpriteFontWriter.cs b/StardewXnbHack/Framework/Writers/SpriteFontWriter.cs index b64f4c4..4982937 100644 --- a/StardewXnbHack/Framework/Writers/SpriteFontWriter.cs +++ b/StardewXnbHack/Framework/Writers/SpriteFontWriter.cs @@ -3,91 +3,90 @@ using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Toolkit.Utilities; -namespace StardewXnbHack.Framework.Writers +namespace StardewXnbHack.Framework.Writers; + +/// Writes assets to disk. +internal class SpriteFontWriter : BaseAssetWriter { - /// Writes assets to disk. - internal class SpriteFontWriter : BaseAssetWriter + /********* + ** Public methods + *********/ + /// Whether the writer can handle a given asset. + /// The asset value. + public override bool CanWrite(object asset) { - /********* - ** Public methods - *********/ - /// Whether the writer can handle a given asset. - /// The asset value. - public override bool CanWrite(object asset) - { - return asset is SpriteFont; - } + return asset is SpriteFont; + } - /// Write an asset instance to disk. - /// The asset value. - /// The absolute path to the export file, without the file extension. - /// The relative path within the content folder. - /// The operating system running the unpacker. - /// An error phrase indicating why writing to disk failed (if applicable). - /// Returns whether writing to disk completed successfully. - public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) - { - SpriteFont font = (SpriteFont)asset; + /// Write an asset instance to disk. + /// The asset value. + /// The absolute path to the export file, without the file extension. + /// The relative path within the content folder. + /// The operating system running the unpacker. + /// An error phrase indicating why writing to disk failed (if applicable). + /// Returns whether writing to disk completed successfully. + public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) + { + SpriteFont font = (SpriteFont)asset; - // get texture - Texture2D texture = font.Texture; + // get texture + Texture2D texture = font.Texture; - // save texture - using (Stream stream = File.Create($"{toPathWithoutExtension}.png")) + // save texture + using (Stream stream = File.Create($"{toPathWithoutExtension}.png")) + { + if (texture.Format == SurfaceFormat.Dxt3) // MonoGame can't read DXT3 textures directly, need to export through GPU { - if (texture.Format == SurfaceFormat.Dxt3) // MonoGame can't read DXT3 textures directly, need to export through GPU - { - using RenderTarget2D renderTarget = this.RenderWithGpu(texture); - renderTarget.SaveAsPng(stream, texture.Width, texture.Height); - } - else - texture.SaveAsPng(stream, texture.Width, texture.Height); + using RenderTarget2D renderTarget = this.RenderWithGpu(texture); + renderTarget.SaveAsPng(stream, texture.Width, texture.Height); } - - // save font data - var data = new - { - font.LineSpacing, - font.Spacing, - font.DefaultCharacter, - font.Characters, - Glyphs = font.GetGlyphs() - }; - File.WriteAllText($"{toPathWithoutExtension}.{this.GetDataExtension()}", this.FormatData(data)); - - error = null; - return true; + else + texture.SaveAsPng(stream, texture.Width, texture.Height); } - - /********* - ** Private methods - *********/ - /// Draw a texture to a GPU render target. - /// The texture to draw. - private RenderTarget2D RenderWithGpu(Texture2D texture) + // save font data + var data = new { - // set render target - var gpu = texture.GraphicsDevice; - RenderTarget2D target = new RenderTarget2D(gpu, texture.Width, texture.Height); - gpu.SetRenderTarget(target); + font.LineSpacing, + font.Spacing, + font.DefaultCharacter, + font.Characters, + Glyphs = font.GetGlyphs() + }; + File.WriteAllText($"{toPathWithoutExtension}.{this.GetDataExtension()}", this.FormatData(data)); - // render - try - { - gpu.Clear(Color.Transparent); // set transparent background + error = null; + return true; + } - using SpriteBatch batch = new SpriteBatch(gpu); - batch.Begin(); - batch.Draw(texture, Vector2.Zero, Color.White); - batch.End(); - } - finally - { - gpu.SetRenderTarget(null); - } - return target; + /********* + ** Private methods + *********/ + /// Draw a texture to a GPU render target. + /// The texture to draw. + private RenderTarget2D RenderWithGpu(Texture2D texture) + { + // set render target + var gpu = texture.GraphicsDevice; + RenderTarget2D target = new RenderTarget2D(gpu, texture.Width, texture.Height); + gpu.SetRenderTarget(target); + + // render + try + { + gpu.Clear(Color.Transparent); // set transparent background + + using SpriteBatch batch = new SpriteBatch(gpu); + batch.Begin(); + batch.Draw(texture, Vector2.Zero, Color.White); + batch.End(); } + finally + { + gpu.SetRenderTarget(null); + } + + return target; } } diff --git a/StardewXnbHack/Framework/Writers/TextureWriter.cs b/StardewXnbHack/Framework/Writers/TextureWriter.cs index ab99c25..eb10613 100644 --- a/StardewXnbHack/Framework/Writers/TextureWriter.cs +++ b/StardewXnbHack/Framework/Writers/TextureWriter.cs @@ -3,66 +3,65 @@ using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Toolkit.Utilities; -namespace StardewXnbHack.Framework.Writers +namespace StardewXnbHack.Framework.Writers; + +/// Writes assets to disk. +internal class TextureWriter : BaseAssetWriter { - /// Writes assets to disk. - internal class TextureWriter : BaseAssetWriter + /********* + ** Public methods + *********/ + /// Whether the writer can handle a given asset. + /// The asset value. + public override bool CanWrite(object asset) { - /********* - ** Public methods - *********/ - /// Whether the writer can handle a given asset. - /// The asset value. - public override bool CanWrite(object asset) - { - return asset is Texture2D; - } + return asset is Texture2D; + } - /// Write an asset instance to disk. - /// The asset value. - /// The absolute path to the export file, without the file extension. - /// The relative path within the content folder. - /// The operating system running the unpacker. - /// An error phrase indicating why writing to disk failed (if applicable). - /// Returns whether writing to disk completed successfully. - public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) - { - Texture2D texture = (Texture2D)asset; + /// Write an asset instance to disk. + /// The asset value. + /// The absolute path to the export file, without the file extension. + /// The relative path within the content folder. + /// The operating system running the unpacker. + /// An error phrase indicating why writing to disk failed (if applicable). + /// Returns whether writing to disk completed successfully. + public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) + { + Texture2D texture = (Texture2D)asset; - this.UnpremultiplyTransparency(texture); - using (Stream stream = File.Create($"{toPathWithoutExtension}.png")) - texture.SaveAsPng(stream, texture.Width, texture.Height); - - error = null; - return true; - } + this.UnpremultiplyTransparency(texture); + using (Stream stream = File.Create($"{toPathWithoutExtension}.png")) + texture.SaveAsPng(stream, texture.Width, texture.Height); + error = null; + return true; + } - /********* - ** Private methods - *********/ - /// Reverse premultiplication applied to an image asset by the XNA content pipeline. - /// The texture to adjust. - private void UnpremultiplyTransparency(Texture2D texture) - { - Color[] data = new Color[texture.Width * texture.Height]; - texture.GetData(data); - for (int i = 0; i < data.Length; i++) - { - Color pixel = data[i]; - if (pixel.A == 0) - continue; + /********* + ** Private methods + *********/ + /// Reverse premultiplication applied to an image asset by the XNA content pipeline. + /// The texture to adjust. + private void UnpremultiplyTransparency(Texture2D texture) + { + Color[] data = new Color[texture.Width * texture.Height]; + texture.GetData(data); - data[i] = new Color( - (byte)((pixel.R * 255) / pixel.A), - (byte)((pixel.G * 255) / pixel.A), - (byte)((pixel.B * 255) / pixel.A), - pixel.A - ); // don't use named parameters, which are inconsistent between MonoGame (e.g. 'alpha') and XNA (e.g. 'a') - } + for (int i = 0; i < data.Length; i++) + { + Color pixel = data[i]; + if (pixel.A == 0) + continue; - texture.SetData(data); + data[i] = new Color( + (byte)((pixel.R * 255) / pixel.A), + (byte)((pixel.G * 255) / pixel.A), + (byte)((pixel.B * 255) / pixel.A), + pixel.A + ); // don't use named parameters, which are inconsistent between MonoGame (e.g. 'alpha') and XNA (e.g. 'a') } + + texture.SetData(data); } } diff --git a/StardewXnbHack/Framework/Writers/XmlSourceWriter.cs b/StardewXnbHack/Framework/Writers/XmlSourceWriter.cs index 7480ede..2fd77cb 100644 --- a/StardewXnbHack/Framework/Writers/XmlSourceWriter.cs +++ b/StardewXnbHack/Framework/Writers/XmlSourceWriter.cs @@ -2,35 +2,34 @@ using BmFont; using StardewModdingAPI.Toolkit.Utilities; -namespace StardewXnbHack.Framework.Writers +namespace StardewXnbHack.Framework.Writers; + +/// Writes assets to disk. +internal class XmlSourceWriter : BaseAssetWriter { - /// Writes assets to disk. - internal class XmlSourceWriter : BaseAssetWriter + /********* + ** Public methods + *********/ + /// Whether the writer can handle a given asset. + /// The asset value. + public override bool CanWrite(object asset) { - /********* - ** Public methods - *********/ - /// Whether the writer can handle a given asset. - /// The asset value. - public override bool CanWrite(object asset) - { - return asset is XmlSource; - } + return asset is XmlSource; + } - /// Write an asset instance to disk. - /// The asset value. - /// The absolute path to the export file, without the file extension. - /// The relative path within the content folder. - /// The operating system running the unpacker. - /// An error phrase indicating why writing to disk failed (if applicable). - /// Returns whether writing to disk completed successfully. - public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) - { - XmlSource value = (XmlSource)asset; - File.WriteAllText($"{toPathWithoutExtension}.fnt", value.Source); + /// Write an asset instance to disk. + /// The asset value. + /// The absolute path to the export file, without the file extension. + /// The relative path within the content folder. + /// The operating system running the unpacker. + /// An error phrase indicating why writing to disk failed (if applicable). + /// Returns whether writing to disk completed successfully. + public override bool TryWriteFile(object asset, string toPathWithoutExtension, string relativePath, Platform platform, out string error) + { + XmlSource value = (XmlSource)asset; + File.WriteAllText($"{toPathWithoutExtension}.fnt", value.Source); - error = null; - return true; - } + error = null; + return true; } } diff --git a/StardewXnbHack/Program.cs b/StardewXnbHack/Program.cs index 1f0e1fb..d1a34f1 100644 --- a/StardewXnbHack/Program.cs +++ b/StardewXnbHack/Program.cs @@ -12,250 +12,250 @@ using StardewXnbHack.Framework.Writers; using StardewXnbHack.ProgressHandling; -namespace StardewXnbHack +namespace StardewXnbHack; + +/// The console app entry point. +public static class Program { - /// The console app entry point. - public static class Program + /********* + ** Fields + *********/ + /// The relative paths to search for unresolved assembly files. + private static readonly string[] RelativeAssemblyProbePaths = { - /********* - ** Fields - *********/ - /// The relative paths to search for unresolved assembly files. - private static readonly string[] RelativeAssemblyProbePaths = - { - "", // app directory - "smapi-internal" - }; + "", // app directory + "smapi-internal" + }; - /********* - ** Public methods - *********/ - /// The console app entry method. - /// The command-line arguments. - internal static void Main(string[] args) - { - // set window title - Console.Title = $"StardewXnbHack {Program.GetUnpackerVersion()}"; + /********* + ** Public methods + *********/ + /// The console app entry method. + /// The command-line arguments. + internal static void Main(string[] args) + { + // set window title + Console.Title = $"StardewXnbHack {Program.GetUnpackerVersion()}"; - // check platform - Program.AssertPlatform(); + // check platform + Program.AssertPlatform(); - // Add fallback assembly resolution that loads DLLs from a 'smapi-internal' subfolder, - // so it can be run from the game folder. This must be set before any references to - // game or toolkit types (including IAssetWriter which references the toolkit's - // Platform enum). - AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve; + // Add fallback assembly resolution that loads DLLs from a 'smapi-internal' subfolder, + // so it can be run from the game folder. This must be set before any references to + // game or toolkit types (including IAssetWriter which references the toolkit's + // Platform enum). + AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve; - // launch app - try - { - Program.Run(args); - } - catch (Exception ex) + // launch app + try + { + Program.Run(args); + } + catch (Exception ex) + { + // not in game folder + if (ex is FileNotFoundException fileNotFoundEx) { - // not in game folder - if (ex is FileNotFoundException fileNotFoundEx) + AssemblyName assemblyName = new AssemblyName(fileNotFoundEx.FileName); + if (assemblyName.Name == "Stardew Valley") { - AssemblyName assemblyName = new AssemblyName(fileNotFoundEx.FileName); - if (assemblyName.Name == "Stardew Valley") - { - Console.WriteLine("Oops! StardewXnbHack must be placed in the Stardew Valley game folder.\nSee instructions: https://github.com/Pathoschild/StardewXnbHack#readme."); - DefaultConsoleLogger.PressAnyKeyToExit(); - return; - } + Console.WriteLine("Oops! StardewXnbHack must be placed in the Stardew Valley game folder.\nSee instructions: https://github.com/Pathoschild/StardewXnbHack#readme."); + DefaultConsoleLogger.PressAnyKeyToExit(); + return; } - - // generic unhandled exception - Console.WriteLine("Oops! Something went wrong running the unpacker:"); - Console.WriteLine(ex.ToString()); - DefaultConsoleLogger.PressAnyKeyToExit(); } + + // generic unhandled exception + Console.WriteLine("Oops! Something went wrong running the unpacker:"); + Console.WriteLine(ex.ToString()); + DefaultConsoleLogger.PressAnyKeyToExit(); } + } - /// Unpack all assets in the content folder and store them in the output folder. - /// The command-line arguments. - /// The game instance through which to unpack files, or null to launch a temporary internal instance. - /// The absolute path to the game folder, or null to auto-detect it. - /// Get a custom progress update logger, or null to use the default console logging. Receives the unpack context and default logger as arguments. - /// Whether the default logger should show a 'press any key to exit' prompt when it finishes. - public static void Run(string[] args, GameRunner game = null, string gamePath = null, Func getLogger = null, bool showPressAnyKeyToExit = true) - { - // init logging - UnpackContext context = new UnpackContext(); - IProgressLogger logger = new DefaultConsoleLogger(context, showPressAnyKeyToExit); + /// Unpack all assets in the content folder and store them in the output folder. + /// The command-line arguments. + /// The game instance through which to unpack files, or null to launch a temporary internal instance. + /// The absolute path to the game folder, or null to auto-detect it. + /// Get a custom progress update logger, or null to use the default console logging. Receives the unpack context and default logger as arguments. + /// Whether the default logger should show a 'press any key to exit' prompt when it finishes. + public static void Run(string[] args, GameRunner game = null, string gamePath = null, Func getLogger = null, bool showPressAnyKeyToExit = true) + { + // init logging + UnpackContext context = new UnpackContext(); + IProgressLogger logger = new DefaultConsoleLogger(context, showPressAnyKeyToExit); - try - { - // get override logger - if (getLogger != null) - logger = getLogger(context, logger); + try + { + // get override logger + if (getLogger != null) + logger = getLogger(context, logger); - // read command-line arguments - bool omitDefaultFields = args.Contains("--clean"); + // read command-line arguments + bool omitDefaultFields = args.Contains("--clean"); - // start log - logger.OnStepChanged(ProgressStep.Started, $"Running StardewXnbHack {Program.GetUnpackerVersion()}.{(omitDefaultFields ? " Special options: omit default fields." : "")}"); + // start log + logger.OnStepChanged(ProgressStep.Started, $"Running StardewXnbHack {Program.GetUnpackerVersion()}.{(omitDefaultFields ? " Special options: omit default fields." : "")}"); - // start timer - Stopwatch timer = new Stopwatch(); - timer.Start(); + // start timer + Stopwatch timer = new Stopwatch(); + timer.Start(); - // get asset writers - var assetWriters = new IAssetWriter[] - { - new MapWriter(), - new SpriteFontWriter(), - new TextureWriter(), - new XmlSourceWriter(), - new DataWriter(omitDefaultFields) // check last due to more expensive CanWrite - }; - - // get paths - var platform = new PlatformContext(); + // get asset writers + var assetWriters = new IAssetWriter[] + { + new MapWriter(), + new SpriteFontWriter(), + new TextureWriter(), + new XmlSourceWriter(), + new DataWriter(omitDefaultFields) // check last due to more expensive CanWrite + }; + + // get paths + var platform = new PlatformContext(); + { + if (platform.TryDetectGamePaths(gamePath, out gamePath, out string contentPath)) { - if (platform.TryDetectGamePaths(gamePath, out gamePath, out string contentPath)) - { - context.GamePath = gamePath; - context.ContentPath = contentPath; - } - else - { - logger.OnFatalError(gamePath == null - ? "Can't find the Stardew Valley folder. Try running StardewXnbHack from the game folder instead." - : $"Can't find the content folder for the game at {gamePath}. Is the game installed correctly?" - ); - return; - } + context.GamePath = gamePath; + context.ContentPath = contentPath; } - context.ExportPath = Path.Combine(context.GamePath, "Content (unpacked)"); - logger.OnStepChanged(ProgressStep.GameFound, $"Found game folder: {context.GamePath}."); - - // symlink files on Linux/Mac - if (platform.Is(Platform.Linux, Platform.Mac)) + else { - foreach (string dirName in new[] { "lib", "lib64" }) - { - string fullPath = Path.Combine(context.GamePath, dirName); - if (!Directory.Exists(dirName)) - Process.Start("ln", $"-sf \"{fullPath}\""); - } + logger.OnFatalError(gamePath == null + ? "Can't find the Stardew Valley folder. Try running StardewXnbHack from the game folder instead." + : $"Can't find the content folder for the game at {gamePath}. Is the game installed correctly?" + ); + return; } + } + context.ExportPath = Path.Combine(context.GamePath, "Content (unpacked)"); + logger.OnStepChanged(ProgressStep.GameFound, $"Found game folder: {context.GamePath}."); - // load game instance - bool disposeGame = false; - if (game == null) + // symlink files on Linux/Mac + if (platform.Is(Platform.Linux, Platform.Mac)) + { + foreach (string dirName in new[] { "lib", "lib64" }) { - logger.OnStepChanged(ProgressStep.LoadingGameInstance, "Loading game instance..."); - game = Program.CreateTemporaryGameInstance(platform, context.ContentPath); - disposeGame = true; + string fullPath = Path.Combine(context.GamePath, dirName); + if (!Directory.Exists(dirName)) + Process.Start("ln", $"-sf \"{fullPath}\""); } + } - // unpack files - try + // load game instance + bool disposeGame = false; + if (game == null) + { + logger.OnStepChanged(ProgressStep.LoadingGameInstance, "Loading game instance..."); + game = Program.CreateTemporaryGameInstance(platform, context.ContentPath); + disposeGame = true; + } + + // unpack files + try + { + logger.OnStepChanged(ProgressStep.UnpackingFiles, $"Unpacking files for Stardew Valley {Program.GetGameVersion()}..."); + + // collect files + DirectoryInfo contentDir = new DirectoryInfo(context.ContentPath); + FileInfo[] files = contentDir.EnumerateFiles("*.xnb", SearchOption.AllDirectories).ToArray(); + context.Files = files; + + // write assets + foreach (FileInfo file in files) { - logger.OnStepChanged(ProgressStep.UnpackingFiles, $"Unpacking files for Stardew Valley {Program.GetGameVersion()}..."); + // prepare paths + string assetName = file.FullName.Substring(context.ContentPath.Length + 1, file.FullName.Length - context.ContentPath.Length - 5); // remove root path + .xnb extension + string relativePath = $"{assetName}.xnb"; + string fileExportPath = Path.Combine(context.ExportPath, assetName); + Directory.CreateDirectory(Path.GetDirectoryName(fileExportPath)); + + // fallback logic + void ExportRawXnb() + { + File.Copy(file.FullName, $"{fileExportPath}.xnb", overwrite: true); + } - // collect files - DirectoryInfo contentDir = new DirectoryInfo(context.ContentPath); - FileInfo[] files = contentDir.EnumerateFiles("*.xnb", SearchOption.AllDirectories).ToArray(); - context.Files = files; + // show progress bar + logger.OnFileUnpacking(assetName); - // write assets - foreach (FileInfo file in files) + // read asset + object asset; + try { - // prepare paths - string assetName = file.FullName.Substring(context.ContentPath.Length + 1, file.FullName.Length - context.ContentPath.Length - 5); // remove root path + .xnb extension - string relativePath = $"{assetName}.xnb"; - string fileExportPath = Path.Combine(context.ExportPath, assetName); - Directory.CreateDirectory(Path.GetDirectoryName(fileExportPath)); - - // fallback logic - void ExportRawXnb() - { - File.Copy(file.FullName, $"{fileExportPath}.xnb", overwrite: true); - } + asset = game.Content.Load(assetName); + } + catch (Exception ex) + { + if (ex.Message == "This does not appear to be a MonoGame MGFX file!") + logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnsupportedFileType, $"{nameof(Effect)} isn't a supported asset type."); + else + logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.ReadError, $"read error: {ex.Message}"); + ExportRawXnb(); + continue; + } - // show progress bar - logger.OnFileUnpacking(assetName); + // write asset + try + { + // get writer + IAssetWriter writer = assetWriters.FirstOrDefault(p => p.CanWrite(asset)); - // read asset - object asset; - try - { - asset = game.Content.Load(assetName); - } - catch (Exception ex) + // write file + if (writer == null) { - if (ex.Message == "This does not appear to be a MonoGame MGFX file!") - logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnsupportedFileType, $"{nameof(Effect)} isn't a supported asset type."); - else - logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.ReadError, $"read error: {ex.Message}"); + logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnsupportedFileType, $"{asset.GetType().Name} isn't a supported asset type."); ExportRawXnb(); - continue; } - - // write asset - try - { - // get writer - IAssetWriter writer = assetWriters.FirstOrDefault(p => p.CanWrite(asset)); - - // write file - if (writer == null) - { - logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnsupportedFileType, $"{asset.GetType().Name} isn't a supported asset type."); - ExportRawXnb(); - } - else if (!writer.TryWriteFile(asset, fileExportPath, assetName, platform.Platform, out string writeError)) - { - logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.WriteError, $"{asset.GetType().Name} file could not be saved: {writeError}."); - ExportRawXnb(); - } - } - catch (Exception ex) - { - logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnknownError, $"unhandled export error: {ex.Message}"); - } - finally + else if (!writer.TryWriteFile(asset, fileExportPath, assetName, platform.Platform, out string writeError)) { - game.Content.Unload(); + logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.WriteError, $"{asset.GetType().Name} file could not be saved: {writeError}."); + ExportRawXnb(); } } + catch (Exception ex) + { + logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnknownError, $"unhandled export error: {ex.Message}"); + } + finally + { + game.Content.Unload(); + } } - finally - { - if (disposeGame) - game.Dispose(); - } - - logger.OnStepChanged(ProgressStep.Done, $"Done! Unpacked {context.Files.Count()} files in {Program.GetHumanTime(timer.Elapsed)}.\nUnpacked into {context.ExportPath}."); - } - catch (Exception ex) - { - logger.OnFatalError($"Unhandled exception: {ex}"); } finally { - logger.OnEnded(); + if (disposeGame) + game.Dispose(); } + + logger.OnStepChanged(ProgressStep.Done, $"Done! Unpacked {context.Files.Count()} files in {Program.GetHumanTime(timer.Elapsed)}.\nUnpacked into {context.ExportPath}."); } + catch (Exception ex) + { + logger.OnFatalError($"Unhandled exception: {ex}"); + } + finally + { + logger.OnEnded(); + } + } - /********* - ** Private methods - *********/ - /// Assert that the current platform matches the one StardewXnbHack was compiled for. - private static void AssertPlatform() - { - bool isWindows = Environment.OSVersion.Platform != PlatformID.MacOSX && Environment.OSVersion.Platform != PlatformID.Unix; + /********* + ** Private methods + *********/ + /// Assert that the current platform matches the one StardewXnbHack was compiled for. + private static void AssertPlatform() + { + bool isWindows = Environment.OSVersion.Platform != PlatformID.MacOSX && Environment.OSVersion.Platform != PlatformID.Unix; #if IS_FOR_WINDOWS - if (!isWindows) - { - Console.WriteLine("Oops! This is the Windows version of StardewXnbHack. Make sure to install the Windows version instead."); - DefaultConsoleLogger.PressAnyKeyToExit(); - } + if (!isWindows) + { + Console.WriteLine("Oops! This is the Windows version of StardewXnbHack. Make sure to install the Windows version instead."); + DefaultConsoleLogger.PressAnyKeyToExit(); + } #else if (isWindows) { @@ -263,132 +263,131 @@ private static void AssertPlatform() DefaultConsoleLogger.PressAnyKeyToExit(); } #endif - } - - /// Method called when assembly resolution fails, which may return a manually resolved assembly. - /// The event sender. - /// The event arguments. - private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) - { - // get assembly name - AssemblyName name = new AssemblyName(e.Name); - if (name.Name == null) - return null; - - // check search folders - foreach (string relativePath in Program.RelativeAssemblyProbePaths) - { - // get absolute path of search folder - string searchPath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), relativePath); - if (!Directory.Exists(searchPath)) - continue; - - // try to resolve DLL - try - { - foreach (FileInfo dll in new DirectoryInfo(searchPath).EnumerateFiles("*.dll")) - { - // get assembly name - string dllAssemblyName; - try - { - dllAssemblyName = AssemblyName.GetAssemblyName(dll.FullName).Name; - } - catch - { - continue; - } - - // check for match - if (name.Name.Equals(dllAssemblyName, StringComparison.OrdinalIgnoreCase)) - return Assembly.LoadFrom(dll.FullName); - } - } - catch (Exception ex) - { - Console.WriteLine($"Error resolving assembly: {ex}"); - return null; - } - } + } + /// Method called when assembly resolution fails, which may return a manually resolved assembly. + /// The event sender. + /// The event arguments. + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) + { + // get assembly name + AssemblyName name = new AssemblyName(e.Name); + if (name.Name == null) return null; - } - /// Create a temporary game instance for the unpacker. - /// The platform-specific context. - /// The absolute path to the content folder to import. - private static GameRunner CreateTemporaryGameInstance(PlatformContext platform, string contentPath) + // check search folders + foreach (string relativePath in Program.RelativeAssemblyProbePaths) { - var foregroundColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.DarkGray; + // get absolute path of search folder + string searchPath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), relativePath); + if (!Directory.Exists(searchPath)) + continue; + // try to resolve DLL try { - GameRunner game = new GameRunner(); - GameRunner.instance = game; - - Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; - - game.RunOneFrame(); + foreach (FileInfo dll in new DirectoryInfo(searchPath).EnumerateFiles("*.dll")) + { + // get assembly name + string dllAssemblyName; + try + { + dllAssemblyName = AssemblyName.GetAssemblyName(dll.FullName).Name; + } + catch + { + continue; + } - return game; + // check for match + if (name.Name.Equals(dllAssemblyName, StringComparison.OrdinalIgnoreCase)) + return Assembly.LoadFrom(dll.FullName); + } } - finally + catch (Exception ex) { - Console.ForegroundColor = foregroundColor; - Console.WriteLine(); + Console.WriteLine($"Error resolving assembly: {ex}"); + return null; } } - /// Get a human-readable representation of elapsed time. - /// The elapsed time. - private static string GetHumanTime(TimeSpan time) + return null; + } + + /// Create a temporary game instance for the unpacker. + /// The platform-specific context. + /// The absolute path to the content folder to import. + private static GameRunner CreateTemporaryGameInstance(PlatformContext platform, string contentPath) + { + var foregroundColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.DarkGray; + + try { - List parts = new List(2); + GameRunner game = new GameRunner(); + GameRunner.instance = game; - if (time.TotalMinutes >= 1) - parts.Add($"{time.TotalMinutes:0} minute{(time.TotalMinutes >= 2 ? "s" : "")}"); - if (time.Seconds > 0) - parts.Add($"{time.Seconds:0} second{(time.Seconds > 1 ? "s" : "")}"); + Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; - return string.Join(" ", parts); - } + game.RunOneFrame(); - /// Get the version string for StardewXnbHack. - private static string GetUnpackerVersion() + return game; + } + finally { - var attribute = typeof(Program).Assembly.GetCustomAttribute(); - return attribute?.InformationalVersion ?? ""; + Console.ForegroundColor = foregroundColor; + Console.WriteLine(); } + } - /// Get the version string for Stardew Valley. - private static string GetGameVersion() - { - StringBuilder version = new(); + /// Get a human-readable representation of elapsed time. + /// The elapsed time. + private static string GetHumanTime(TimeSpan time) + { + List parts = new List(2); - // base version - version.Append(Game1.version); - version.Append(" ("); + if (time.TotalMinutes >= 1) + parts.Add($"{time.TotalMinutes:0} minute{(time.TotalMinutes >= 2 ? "s" : "")}"); + if (time.Seconds > 0) + parts.Add($"{time.Seconds:0} second{(time.Seconds > 1 ? "s" : "")}"); - // build number - { - string buildVersion = typeof(Game1).Assembly.GetName().Version?.ToString() ?? "unknown"; - if (buildVersion.StartsWith($"{Game1.version}.")) - buildVersion = buildVersion.Substring(Game1.version.Length + 1); - version.Append(buildVersion); - } + return string.Join(" ", parts); + } - // version label - if (!string.IsNullOrWhiteSpace(Game1.versionLabel)) - { - version.Append(" \""); - version.Append(Game1.versionLabel); - version.Append('"'); - } + /// Get the version string for StardewXnbHack. + private static string GetUnpackerVersion() + { + var attribute = typeof(Program).Assembly.GetCustomAttribute(); + return attribute?.InformationalVersion ?? ""; + } - version.Append(')'); + /// Get the version string for Stardew Valley. + private static string GetGameVersion() + { + StringBuilder version = new(); + + // base version + version.Append(Game1.version); + version.Append(" ("); + + // build number + { + string buildVersion = typeof(Game1).Assembly.GetName().Version?.ToString() ?? "unknown"; + if (buildVersion.StartsWith($"{Game1.version}.")) + buildVersion = buildVersion.Substring(Game1.version.Length + 1); + version.Append(buildVersion); + } - return version.ToString(); + // version label + if (!string.IsNullOrWhiteSpace(Game1.versionLabel)) + { + version.Append(" \""); + version.Append(Game1.versionLabel); + version.Append('"'); } + + version.Append(')'); + + return version.ToString(); } } diff --git a/StardewXnbHack/ProgressHandling/IProgressLogger.cs b/StardewXnbHack/ProgressHandling/IProgressLogger.cs index 5c2e1f3..041c598 100644 --- a/StardewXnbHack/ProgressHandling/IProgressLogger.cs +++ b/StardewXnbHack/ProgressHandling/IProgressLogger.cs @@ -1,31 +1,30 @@ -namespace StardewXnbHack.ProgressHandling +namespace StardewXnbHack.ProgressHandling; + +/// Logs updates while the unpacker is running. +public interface IProgressLogger { - /// Logs updates while the unpacker is running. - public interface IProgressLogger - { - /********* - ** Methods - *********/ - /// Log an error which halts the unpack process (e.g. game folder missing). - /// The error message indicating why the unpacker halted. - void OnFatalError(string error); + /********* + ** Methods + *********/ + /// Log an error which halts the unpack process (e.g. game folder missing). + /// The error message indicating why the unpacker halted. + void OnFatalError(string error); - /// Log a step transition in the overall unpack process. - /// The new step. - /// The default log message for the step transition. - void OnStepChanged(ProgressStep step, string message); + /// Log a step transition in the overall unpack process. + /// The new step. + /// The default log message for the step transition. + void OnStepChanged(ProgressStep step, string message); - /// Log a file being unpacked. - /// The relative path of the file within the content folder. - void OnFileUnpacking(string relativePath); + /// Log a file being unpacked. + /// The relative path of the file within the content folder. + void OnFileUnpacking(string relativePath); - /// Log a file which couldn't be unpacked. - /// The relative path of the file within the content folder. - /// An error code indicating why unpacking failed. - /// An error message indicating why unpacking failed. - void OnFileUnpackFailed(string relativePath, UnpackFailedReason errorCode, string errorMessage); + /// Log a file which couldn't be unpacked. + /// The relative path of the file within the content folder. + /// An error code indicating why unpacking failed. + /// An error message indicating why unpacking failed. + void OnFileUnpackFailed(string relativePath, UnpackFailedReason errorCode, string errorMessage); - /// The unpacker is done and exiting. - void OnEnded(); - } + /// The unpacker is done and exiting. + void OnEnded(); } diff --git a/StardewXnbHack/ProgressHandling/IUnpackContext.cs b/StardewXnbHack/ProgressHandling/IUnpackContext.cs index 7d618f8..9c106dd 100644 --- a/StardewXnbHack/ProgressHandling/IUnpackContext.cs +++ b/StardewXnbHack/ProgressHandling/IUnpackContext.cs @@ -1,21 +1,20 @@ using System.Collections.Generic; using System.IO; -namespace StardewXnbHack.ProgressHandling +namespace StardewXnbHack.ProgressHandling; + +/// The context info for the current unpack run. +public interface IUnpackContext { - /// The context info for the current unpack run. - public interface IUnpackContext - { - /// The absolute path to the game folder. - string GamePath { get; } + /// The absolute path to the game folder. + string GamePath { get; } - /// The absolute path to the content folder. - string ContentPath { get; } + /// The absolute path to the content folder. + string ContentPath { get; } - /// The absolute path to the folder containing exported assets. - string ExportPath { get; } + /// The absolute path to the folder containing exported assets. + string ExportPath { get; } - /// The files found to unpack. - IEnumerable Files { get; } - } + /// The files found to unpack. + IEnumerable Files { get; } } diff --git a/StardewXnbHack/ProgressHandling/ProgressStep.cs b/StardewXnbHack/ProgressHandling/ProgressStep.cs index 387ef8d..015f768 100644 --- a/StardewXnbHack/ProgressHandling/ProgressStep.cs +++ b/StardewXnbHack/ProgressHandling/ProgressStep.cs @@ -1,21 +1,20 @@ -namespace StardewXnbHack.ProgressHandling +namespace StardewXnbHack.ProgressHandling; + +/// A step in the overall unpack process. +public enum ProgressStep { - /// A step in the overall unpack process. - public enum ProgressStep - { - /// The unpacker has started. - Started, + /// The unpacker has started. + Started, - /// The game folder was located, but unpacking hasn't started yet. - GameFound, + /// The game folder was located, but unpacking hasn't started yet. + GameFound, - /// The temporary game instance is being loaded. - LoadingGameInstance, + /// The temporary game instance is being loaded. + LoadingGameInstance, - /// The files are being unpacked. - UnpackingFiles, + /// The files are being unpacked. + UnpackingFiles, - /// The overall unpack process completed successfully. - Done - } + /// The overall unpack process completed successfully. + Done } diff --git a/StardewXnbHack/ProgressHandling/UnpackFailedReason.cs b/StardewXnbHack/ProgressHandling/UnpackFailedReason.cs index 3f91b72..f1f8c23 100644 --- a/StardewXnbHack/ProgressHandling/UnpackFailedReason.cs +++ b/StardewXnbHack/ProgressHandling/UnpackFailedReason.cs @@ -1,18 +1,17 @@ -namespace StardewXnbHack.ProgressHandling +namespace StardewXnbHack.ProgressHandling; + +/// An error code indicating why unpacking failed for a file. +public enum UnpackFailedReason { - /// An error code indicating why unpacking failed for a file. - public enum UnpackFailedReason - { - /// An error occurred trying to read the raw XNB asset. - ReadError, + /// An error occurred trying to read the raw XNB asset. + ReadError, - /// The XNB asset was successfully loaded, but its file format can't be unpacked. - UnsupportedFileType, + /// The XNB asset was successfully loaded, but its file format can't be unpacked. + UnsupportedFileType, - /// The XNB asset was successfully loaded, but an error occurred trying to save the unpacked file. - WriteError, + /// The XNB asset was successfully loaded, but an error occurred trying to save the unpacked file. + WriteError, - /// An unhandled error occurred. - UnknownError - } + /// An unhandled error occurred. + UnknownError } From 062b64470183c4e0b10410a03e0a7f8cfe03af20 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Nov 2024 19:21:12 -0500 Subject: [PATCH 2/3] update mod build package --- StardewXnbHack/StardewXnbHack.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StardewXnbHack/StardewXnbHack.csproj b/StardewXnbHack/StardewXnbHack.csproj index c095ae3..d757385 100644 --- a/StardewXnbHack/StardewXnbHack.csproj +++ b/StardewXnbHack/StardewXnbHack.csproj @@ -26,7 +26,7 @@ - + From 4d29f5827ba92de332c6173ad605102baabb2ea6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Nov 2024 19:21:12 -0500 Subject: [PATCH 3/3] prepare for release --- StardewXnbHack/StardewXnbHack.csproj | 2 +- release-notes.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/StardewXnbHack/StardewXnbHack.csproj b/StardewXnbHack/StardewXnbHack.csproj index d757385..0e114f7 100644 --- a/StardewXnbHack/StardewXnbHack.csproj +++ b/StardewXnbHack/StardewXnbHack.csproj @@ -5,7 +5,7 @@ MIT https://github.com/Pathoschild/StardewXnbHack git - 1.1.1 + 1.1.2 net6.0 diff --git a/release-notes.md b/release-notes.md index 08ab130..cecdff7 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,11 @@ [← back to readme](README.md) # Release notes +## 1.1.2 +Released 05 November 2024. + +* Updated for SMAPI 4.1.4. + ## 1.1.1 Released 20 August 2024.