diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs
index f07bd8fe..3d6af22b 100644
--- a/src/SMAPI.ModBuildConfig/DeployModTask.cs
+++ b/src/SMAPI.ModBuildConfig/DeployModTask.cs
@@ -92,10 +92,10 @@ public class DeployModTask : Task
{
try
{
- string manifestPath = Path.Combine(this.ProjectDir, "manifest.json");
+ string manifestPath = Path.Combine(this.ProjectDir, BundleFile.ManifestFileName);
if (!new JsonHelper().ReadJsonFileIfExists(manifestPath, out Manifest rawManifest))
{
- this.Log.LogError("[mod build package] The mod's manifest.json file doesn't exist.");
+ this.Log.LogError($"[mod build package] The mod's {BundleFile.ManifestFileName} file doesn't exist.");
return false;
}
manifest = rawManifest;
@@ -104,14 +104,14 @@ public class DeployModTask : Task
{
// log the inner exception, otherwise the message will be generic
Exception exToShow = ex.InnerException ?? ex;
- this.Log.LogError($"[mod build package] The mod's manifest.json file isn't valid JSON: {exToShow.Message}");
+ this.Log.LogError($"[mod build package] The mod's {BundleFile.ManifestFileName} file isn't valid JSON: {exToShow.Message}");
return false;
}
// validate manifest fields
if (!ManifestValidator.TryValidateFields(manifest, out string error))
{
- this.Log.LogError($"[mod build package] The mod's manifest.json file is invalid: {error}");
+ this.Log.LogError($"[mod build package] The mod's {BundleFile.ManifestFileName} file is invalid: {error}");
return false;
}
}
@@ -301,19 +301,12 @@ public class DeployModTask : Task
{
foreach (var mod in modPackages)
{
- string relativePath = modPackages.Count == 1
+ string folderPath = modPackages.Count == 1
? outputPath
: Path.Combine(outputPath, this.EscapeInvalidFilenameCharacters(mod.Key));
- foreach (var file in mod.Value.GetFiles())
- {
- string fromPath = file.Value.FullName;
- string toPath = Path.Combine(relativePath, file.Key);
-
- Directory.CreateDirectory(Path.GetDirectoryName(toPath)!);
-
- File.Copy(fromPath, toPath, overwrite: true);
- }
+ foreach (BundleFile from in mod.Value.GetFiles())
+ from.CopyToFolder(folderPath);
}
}
@@ -330,21 +323,19 @@ public class DeployModTask : Task
foreach (var mod in modPackages)
{
string modFolder = this.EscapeInvalidFilenameCharacters(mod.Key);
- foreach (var file in mod.Value.GetFiles())
+ foreach (BundleFile from in mod.Value.GetFiles())
{
- string relativePath = file.Key;
+ string relativePath = from.RelativePath;
+
if (relativePath.Contains('\\'))
relativePath = string.Join("/", PathUtilities.GetSegments(relativePath)); // zip files use forward slashes regardless of OS
- FileInfo fileInfo = file.Value;
-
string archivePath = modPackages.Count == 1
? $"{modFolder}/{relativePath}"
: $"{this.ModFolderName}/{modFolder}/{relativePath}";
- using Stream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
using Stream fileStreamInZip = archive.CreateEntry(archivePath).Open();
- fileStream.CopyTo(fileStreamInZip);
+ from.CopyToStream(fileStreamInZip);
}
}
}
diff --git a/src/SMAPI.ModBuildConfig/Framework/BundleFile.cs b/src/SMAPI.ModBuildConfig/Framework/BundleFile.cs
new file mode 100644
index 00000000..a6c4eca0
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/Framework/BundleFile.cs
@@ -0,0 +1,64 @@
+using System;
+using System.IO;
+
+namespace StardewModdingAPI.ModBuildConfig.Framework;
+
+/// A file that should be deployed or zipped as part of a mod.
+internal class BundleFile
+{
+ /*********
+ ** Accessors
+ *********/
+ /// The name of the manifest file.
+ public const string ManifestFileName = "manifest.json";
+
+ /// The file's relative path within the mod.
+ public string RelativePath { get; }
+
+ /// The file to copy from.
+ public FileInfo File { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ /// The file's relative path within the mod.
+ /// The file to copy from.
+ public BundleFile(string relativePath, FileInfo file)
+ {
+ this.RelativePath = relativePath;
+ this.File = file;
+ }
+
+ /// Get whether this entry is for the mod's manifest.json file.
+ public bool IsModManifest()
+ {
+ return BundleFile.IsModManifest(this.RelativePath);
+ }
+
+ /// Copy the file into a destination folder.
+ /// The folder path to use as the base for the .
+ public void CopyToFolder(string folderPath)
+ {
+ string toPath = Path.Combine(folderPath, this.RelativePath);
+
+ Directory.CreateDirectory(Path.GetDirectoryName(toPath)!);
+ this.File.CopyTo(toPath, overwrite: true);
+ }
+
+ /// Copy the file's contents into a stream.
+ /// The stream into which to write the file's contents.
+ public void CopyToStream(Stream stream)
+ {
+ using FileStream fromStream = this.File.OpenRead();
+ fromStream.CopyTo(stream);
+ }
+
+ /// Get whether a relative path is for the mod's manifest.json file.
+ /// The relative path within the mod folder within the mod's folder.
+ public static bool IsModManifest(string relativePath)
+ {
+ return string.Equals(relativePath, BundleFile.ManifestFileName, StringComparison.OrdinalIgnoreCase);
+ }
+}
diff --git a/src/SMAPI.ModBuildConfig/Framework/ContentPackFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ContentPackFileManager.cs
index 4e222c77..6d6b6e23 100644
--- a/src/SMAPI.ModBuildConfig/Framework/ContentPackFileManager.cs
+++ b/src/SMAPI.ModBuildConfig/Framework/ContentPackFileManager.cs
@@ -18,11 +18,8 @@ internal class ContentPackFileManager : IModFileManager
/*********
** Fields
*********/
- /// The name of the manifest file.
- private readonly string ManifestFileName = "manifest.json";
-
/// The files that are part of the package.
- private readonly Dictionary Files = new(StringComparer.OrdinalIgnoreCase);
+ private readonly List Files = [];
/*********
@@ -44,37 +41,43 @@ internal class ContentPackFileManager : IModFileManager
throw GetError($"that folder doesn't exist at {projectDirInfo.FullName}");
// collect files
- foreach (FileInfo entry in projectDirInfo.GetFiles("*", SearchOption.AllDirectories))
+ BundleFile manifestEntry = null;
+ foreach (FileInfo file in projectDirInfo.GetFiles("*", SearchOption.AllDirectories))
{
- string relativePath = PathUtilities.GetRelativePath(projectDirInfo.FullName, entry.FullName);
- FileInfo file = entry;
+ string relativePath = PathUtilities.GetRelativePath(projectDirInfo.FullName, file.FullName);
if (!this.ShouldIgnore(file, relativePath, ignoreFilePaths, ignoreFilePatterns))
- this.Files[relativePath] = file;
+ {
+ BundleFile entry = new(relativePath, file);
+ this.Files.Add(entry);
+
+ if (manifestEntry is null && entry.IsModManifest())
+ manifestEntry = entry;
+ }
}
// validate manifest
if (validateManifest)
{
// get manifest file
- if (!this.Files.TryGetValue(this.ManifestFileName, out FileInfo manifestFile))
- throw GetError($"it has no {this.ManifestFileName} file");
+ if (manifestEntry is null)
+ throw GetError($"it has no {BundleFile.ManifestFileName} file");
// parse file
Manifest manifest;
try
{
- new JsonHelper().ReadJsonFileIfExists(manifestFile.FullName, out Manifest rawManifest);
+ new JsonHelper().ReadJsonFileIfExists(manifestEntry.File.FullName, out Manifest rawManifest);
manifest = rawManifest;
}
catch (JsonReaderException ex)
{
- throw GetError($"its {this.ManifestFileName} file isn't valid JSON: {ex.InnerException?.Message ?? ex.Message}");
+ throw GetError($"its {BundleFile.ManifestFileName} file isn't valid JSON: {ex.InnerException?.Message ?? ex.Message}");
}
// validate manifest fields
if (!ManifestValidator.TryValidateFields(manifest, out string error))
- throw GetError($"its {this.ManifestFileName} file is invalid: {error}");
+ throw GetError($"its {BundleFile.ManifestFileName} file is invalid: {error}");
// validate version
if (version == null)
@@ -82,7 +85,7 @@ internal class ContentPackFileManager : IModFileManager
if (!SemanticVersion.TryParse(version, out ISemanticVersion requiredVersion))
throw GetError($"the provided Version value '{version}' isn't a valid semantic version");
if (manifest.Version.CompareTo(requiredVersion) != 0)
- throw GetError($"its {this.ManifestFileName} has version '{manifest.Version}' instead of the required '{requiredVersion}'");
+ throw GetError($"its {BundleFile.ManifestFileName} has version '{manifest.Version}' instead of the required '{requiredVersion}'");
}
UserErrorException GetError(string reasonPhrase)
@@ -92,9 +95,9 @@ internal class ContentPackFileManager : IModFileManager
}
///
- public IDictionary GetFiles()
+ public IEnumerable GetFiles()
{
- return new Dictionary(this.Files, StringComparer.OrdinalIgnoreCase);
+ return this.Files;
}
/// Get whether a content file should be ignored.
diff --git a/src/SMAPI.ModBuildConfig/Framework/IModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/IModFileManager.cs
index 1ce6cdc5..1eb0e470 100644
--- a/src/SMAPI.ModBuildConfig/Framework/IModFileManager.cs
+++ b/src/SMAPI.ModBuildConfig/Framework/IModFileManager.cs
@@ -1,11 +1,10 @@
using System.Collections.Generic;
-using System.IO;
namespace StardewModdingAPI.ModBuildConfig.Framework;
/// Manages the files that are part of a mod in the release package.
-public interface IModFileManager
+internal interface IModFileManager
{
/// Get the files in the mod package.
- public IDictionary GetFiles();
+ public IEnumerable GetFiles();
}
diff --git a/src/SMAPI.ModBuildConfig/Framework/MainModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/MainModFileManager.cs
index 5bd15085..f25832fa 100644
--- a/src/SMAPI.ModBuildConfig/Framework/MainModFileManager.cs
+++ b/src/SMAPI.ModBuildConfig/Framework/MainModFileManager.cs
@@ -13,14 +13,11 @@ internal class MainModFileManager : IModFileManager
/*********
** Fields
*********/
- /// The name of the manifest file.
- private readonly string ManifestFileName = "manifest.json";
-
/// The files that are part of the package.
- private readonly Dictionary Files = new(StringComparer.OrdinalIgnoreCase);
+ private readonly List Files = [];
/// The file extensions used by assembly files.
- private readonly ISet AssemblyFileExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
+ private readonly HashSet AssemblyFileExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".dll",
".exe",
@@ -29,8 +26,8 @@ internal class MainModFileManager : IModFileManager
};
/// The DLLs which match the type.
- private readonly ISet GameDllNames = new HashSet
- {
+ private readonly HashSet GameDllNames =
+ [
// SMAPI
"0Harmony",
"Mono.Cecil",
@@ -56,7 +53,7 @@ internal class MainModFileManager : IModFileManager
"Steamworks.NET",
"TextCopy",
"xTile"
- };
+ ];
/*********
@@ -80,33 +77,36 @@ internal class MainModFileManager : IModFileManager
throw new UserErrorException("Could not create mod package because no build output was found.");
// collect files
- foreach (Tuple entry in this.GetPossibleFiles(projectDir, targetDir))
+ BundleFile manifestEntry = null;
+ foreach (BundleFile entry in this.GetPossibleFiles(projectDir, targetDir))
{
- string relativePath = entry.Item1;
- FileInfo file = entry.Item2;
+ if (!this.ShouldIgnore(entry.File, entry.RelativePath, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, modDllName))
+ {
+ this.Files.Add(entry);
- if (!this.ShouldIgnore(file, relativePath, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, modDllName))
- this.Files[relativePath] = file;
+ if (manifestEntry is null && entry.IsModManifest())
+ manifestEntry = entry;
+ }
}
// check for required files
if (validateRequiredModFiles)
{
// manifest
- if (!this.Files.ContainsKey(this.ManifestFileName))
- throw new UserErrorException($"Could not create mod package because no {this.ManifestFileName} was found in the project or build output.");
+ if (manifestEntry is null)
+ throw new UserErrorException($"Could not create mod package because no {BundleFile.ManifestFileName} was found in the project or build output.");
// DLL
// ReSharper disable once SimplifyLinqExpression
- if (!this.Files.Any(p => !p.Key.EndsWith(".dll")))
+ if (!this.Files.Any(p => !p.RelativePath.EndsWith(".dll")))
throw new UserErrorException("Could not create mod package because no .dll file was found in the project or build output.");
}
}
///
- public IDictionary GetFiles()
+ public IEnumerable GetFiles()
{
- return new Dictionary(this.Files, StringComparer.OrdinalIgnoreCase);
+ return this.Files;
}
@@ -117,15 +117,15 @@ internal class MainModFileManager : IModFileManager
/// The folder containing the project files.
/// The folder containing the build output.
/// Returns tuples containing the relative path within the mod folder, and the file to copy to it.
- private IEnumerable> GetPossibleFiles(string projectDir, string targetDir)
+ private IEnumerable GetPossibleFiles(string projectDir, string targetDir)
{
// project manifest
bool hasProjectManifest = false;
{
- FileInfo manifest = new(Path.Combine(projectDir, this.ManifestFileName));
+ FileInfo manifest = new(Path.Combine(projectDir, BundleFile.ManifestFileName));
if (manifest.Exists)
{
- yield return Tuple.Create(this.ManifestFileName, manifest);
+ yield return new BundleFile(BundleFile.ManifestFileName, manifest);
hasProjectManifest = true;
}
}
@@ -138,7 +138,7 @@ internal class MainModFileManager : IModFileManager
foreach (FileInfo file in translationsFolder.EnumerateFiles("*", SearchOption.AllDirectories))
{
string relativePath = PathUtilities.GetRelativePath(projectDir, file.FullName);
- yield return Tuple.Create(relativePath, file);
+ yield return new BundleFile(relativePath, file);
}
hasProjectTranslations = true;
}
@@ -151,7 +151,7 @@ internal class MainModFileManager : IModFileManager
foreach (FileInfo file in assetsFolder.EnumerateFiles("*", SearchOption.AllDirectories))
{
string relativePath = PathUtilities.GetRelativePath(projectDir, file.FullName);
- yield return Tuple.Create(relativePath, file);
+ yield return new BundleFile(relativePath, file);
}
hasAssetsFolder = true;
}
@@ -165,7 +165,7 @@ internal class MainModFileManager : IModFileManager
string[] segments = PathUtilities.GetSegments(relativePath);
// prefer project manifest/i18n/assets files
- if (hasProjectManifest && this.EqualsInvariant(relativePath, this.ManifestFileName))
+ if (hasProjectManifest && BundleFile.IsModManifest(relativePath))
continue;
if (hasProjectTranslations && this.EqualsInvariant(segments[0], "i18n"))
continue;
@@ -173,7 +173,7 @@ internal class MainModFileManager : IModFileManager
continue;
// add file
- yield return Tuple.Create(relativePath, file);
+ yield return new BundleFile(relativePath, file);
}
}