summaryrefslogtreecommitdiff
path: root/src/SMAPI.ModBuildConfig
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI.ModBuildConfig')
-rw-r--r--src/SMAPI.ModBuildConfig/DeployModTask.cs154
-rw-r--r--src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs174
-rw-r--r--src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs16
-rw-r--r--src/SMAPI.ModBuildConfig/Properties/AssemblyInfo.cs9
-rw-r--r--src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj60
-rw-r--r--src/SMAPI.ModBuildConfig/assets/nuget-icon.pdnbin0 -> 7401 bytes
-rw-r--r--src/SMAPI.ModBuildConfig/assets/nuget-icon.pngbin0 -> 5054 bytes
-rw-r--r--src/SMAPI.ModBuildConfig/build/smapi.targets144
-rw-r--r--src/SMAPI.ModBuildConfig/package.nuspec32
9 files changed, 589 insertions, 0 deletions
diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs
new file mode 100644
index 00000000..a5725a81
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/DeployModTask.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using StardewModdingAPI.ModBuildConfig.Framework;
+
+namespace StardewModdingAPI.ModBuildConfig
+{
+ /// <summary>A build task which deploys the mod files and prepares a release zip.</summary>
+ public class DeployModTask : Task
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The name of the mod folder.</summary>
+ [Required]
+ public string ModFolderName { get; set; }
+
+ /// <summary>The absolute or relative path to the folder which should contain the generated zip file.</summary>
+ [Required]
+ public string ModZipPath { get; set; }
+
+ /// <summary>The folder containing the project files.</summary>
+ [Required]
+ public string ProjectDir { get; set; }
+
+ /// <summary>The folder containing the build output.</summary>
+ [Required]
+ public string TargetDir { get; set; }
+
+ /// <summary>The folder containing the game files.</summary>
+ [Required]
+ public string GameDir { get; set; }
+
+ /// <summary>Whether to enable copying the mod files into the game's Mods folder.</summary>
+ [Required]
+ public bool EnableModDeploy { get; set; }
+
+ /// <summary>Whether to enable the release zip.</summary>
+ [Required]
+ public bool EnableModZip { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>When overridden in a derived class, executes the task.</summary>
+ /// <returns>true if the task successfully executed; otherwise, false.</returns>
+ public override bool Execute()
+ {
+ if (!this.EnableModDeploy && !this.EnableModZip)
+ return true; // nothing to do
+
+ try
+ {
+ // get mod info
+ ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir);
+
+ // deploy mod files
+ if (this.EnableModDeploy)
+ {
+ string outputPath = Path.Combine(this.GameDir, "Mods", this.EscapeInvalidFilenameCharacters(this.ModFolderName));
+ this.Log.LogMessage(MessageImportance.High, $"The mod build package is copying the mod files to {outputPath}...");
+ this.CreateModFolder(package.GetFiles(), outputPath);
+ }
+
+ // create release zip
+ if (this.EnableModZip)
+ {
+ this.Log.LogMessage(MessageImportance.High, $"The mod build package is generating a release zip at {this.ModZipPath} for {this.ModFolderName}...");
+ this.CreateReleaseZip(package.GetFiles(), this.ModFolderName, package.GetManifestVersion(), this.ModZipPath);
+ }
+
+ return true;
+ }
+ catch (UserErrorException ex)
+ {
+ this.Log.LogErrorFromException(ex);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ this.Log.LogError($"The mod build package failed trying to deploy the mod.\n{ex}");
+ return false;
+ }
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Copy the mod files into the game's mod folder.</summary>
+ /// <param name="files">The files to include.</param>
+ /// <param name="modFolderPath">The folder path to create with the mod files.</param>
+ private void CreateModFolder(IDictionary<string, FileInfo> files, string modFolderPath)
+ {
+ foreach (var entry in files)
+ {
+ string fromPath = entry.Value.FullName;
+ string toPath = Path.Combine(modFolderPath, entry.Key);
+
+ // ReSharper disable once AssignNullToNotNullAttribute -- not applicable in this context
+ Directory.CreateDirectory(Path.GetDirectoryName(toPath));
+
+ File.Copy(fromPath, toPath, overwrite: true);
+ }
+ }
+
+ /// <summary>Create a release zip in the recommended format for uploading to mod sites.</summary>
+ /// <param name="files">The files to include.</param>
+ /// <param name="modName">The name of the mod.</param>
+ /// <param name="modVersion">The mod version string.</param>
+ /// <param name="outputFolderPath">The absolute or relative path to the folder which should contain the generated zip file.</param>
+ private void CreateReleaseZip(IDictionary<string, FileInfo> files, string modName, string modVersion, string outputFolderPath)
+ {
+ // get names
+ string zipName = this.EscapeInvalidFilenameCharacters($"{modName} {modVersion}.zip");
+ string folderName = this.EscapeInvalidFilenameCharacters(modName);
+ string zipPath = Path.Combine(outputFolderPath, zipName);
+
+ // create zip file
+ Directory.CreateDirectory(outputFolderPath);
+ using (Stream zipStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write))
+ using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create))
+ {
+ foreach (var fileEntry in files)
+ {
+ string relativePath = fileEntry.Key;
+ FileInfo file = fileEntry.Value;
+
+ // get file info
+ string filePath = file.FullName;
+ string entryName = folderName + '/' + relativePath.Replace(Path.DirectorySeparatorChar, '/');
+
+ // add to zip
+ using (Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+ using (Stream fileStreamInZip = archive.CreateEntry(entryName).Open())
+ fileStream.CopyTo(fileStreamInZip);
+ }
+ }
+ }
+
+ /// <summary>Get a copy of a filename with all invalid filename characters substituted.</summary>
+ /// <param name="name">The filename.</param>
+ private string EscapeInvalidFilenameCharacters(string name)
+ {
+ foreach (char invalidChar in Path.GetInvalidFileNameChars())
+ name = name.Replace(invalidChar, '.');
+ return name;
+ }
+ }
+}
diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs
new file mode 100644
index 00000000..64262dc2
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs
@@ -0,0 +1,174 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Web.Script.Serialization;
+using StardewModdingAPI.Common;
+
+namespace StardewModdingAPI.ModBuildConfig.Framework
+{
+ /// <summary>Manages the files that are part of a mod package.</summary>
+ internal class ModFileManager
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The name of the manifest file.</summary>
+ private readonly string ManifestFileName = "manifest.json";
+
+ /// <summary>The files that are part of the package.</summary>
+ private readonly IDictionary<string, FileInfo> Files;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="projectDir">The folder containing the project files.</param>
+ /// <param name="targetDir">The folder containing the build output.</param>
+ /// <exception cref="UserErrorException">The mod package isn't valid.</exception>
+ public ModFileManager(string projectDir, string targetDir)
+ {
+ this.Files = new Dictionary<string, FileInfo>(StringComparer.InvariantCultureIgnoreCase);
+
+ // validate paths
+ if (!Directory.Exists(projectDir))
+ throw new UserErrorException("Could not create mod package because the project folder wasn't found.");
+ if (!Directory.Exists(targetDir))
+ throw new UserErrorException("Could not create mod package because no build output was found.");
+
+ // project manifest
+ bool hasProjectManifest = false;
+ {
+ FileInfo manifest = new FileInfo(Path.Combine(projectDir, "manifest.json"));
+ if (manifest.Exists)
+ {
+ this.Files[this.ManifestFileName] = manifest;
+ hasProjectManifest = true;
+ }
+ }
+
+ // project i18n files
+ bool hasProjectTranslations = false;
+ DirectoryInfo translationsFolder = new DirectoryInfo(Path.Combine(projectDir, "i18n"));
+ if (translationsFolder.Exists)
+ {
+ foreach (FileInfo file in translationsFolder.EnumerateFiles())
+ this.Files[Path.Combine("i18n", file.Name)] = file;
+ hasProjectTranslations = true;
+ }
+
+ // build output
+ DirectoryInfo buildFolder = new DirectoryInfo(targetDir);
+ foreach (FileInfo file in buildFolder.EnumerateFiles("*", SearchOption.AllDirectories))
+ {
+ // get relative paths
+ string relativePath = file.FullName.Replace(buildFolder.FullName, "");
+ string relativeDirPath = file.Directory.FullName.Replace(buildFolder.FullName, "");
+
+ // prefer project manifest/i18n files
+ if (hasProjectManifest && this.EqualsInvariant(relativePath, this.ManifestFileName))
+ continue;
+ if (hasProjectTranslations && this.EqualsInvariant(relativeDirPath, "i18n"))
+ continue;
+
+ // ignore release zips
+ if (this.EqualsInvariant(file.Extension, ".zip"))
+ continue;
+
+ // ignore Json.NET (bundled into SMAPI)
+ if (this.EqualsInvariant(file.Name, "Newtonsoft.Json.dll") || this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml"))
+ continue;
+
+ // add file
+ this.Files[relativePath] = file;
+ }
+
+ // check for missing 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.");
+
+ // check for missing DLL
+ // ReSharper disable once SimplifyLinqExpression
+ if (!this.Files.Any(p => !p.Key.EndsWith(".dll")))
+ throw new UserErrorException("Could not create mod package because no .dll file was found in the project or build output.");
+ }
+
+ /// <summary>Get the files in the mod package.</summary>
+ public IDictionary<string, FileInfo> GetFiles()
+ {
+ return new Dictionary<string, FileInfo>(this.Files, StringComparer.InvariantCultureIgnoreCase);
+ }
+
+ /// <summary>Get a semantic version from the mod manifest.</summary>
+ /// <exception cref="UserErrorException">The manifest is missing or invalid.</exception>
+ public string GetManifestVersion()
+ {
+ // get manifest file
+ if (!this.Files.TryGetValue(this.ManifestFileName, out FileInfo manifestFile))
+ throw new InvalidOperationException($"The mod does not have a {this.ManifestFileName} file."); // shouldn't happen since we validate in constructor
+
+ // read content
+ string json = File.ReadAllText(manifestFile.FullName);
+ if (string.IsNullOrWhiteSpace(json))
+ throw new UserErrorException("The mod's manifest must not be empty.");
+
+ // parse JSON
+ IDictionary<string, object> data;
+ try
+ {
+ data = this.Parse(json);
+ }
+ catch (Exception ex)
+ {
+ throw new UserErrorException($"The mod's manifest couldn't be parsed. It doesn't seem to be valid JSON.\n{ex}");
+ }
+
+ // get version field
+ object versionObj = data.ContainsKey("Version") ? data["Version"] : null;
+ if (versionObj == null)
+ throw new UserErrorException("The mod's manifest must have a version field.");
+
+ // get version string
+ if (versionObj is IDictionary<string, object> versionFields) // SMAPI 1.x
+ {
+ int major = versionFields.ContainsKey("MajorVersion") ? (int)versionFields["MajorVersion"] : 0;
+ int minor = versionFields.ContainsKey("MinorVersion") ? (int)versionFields["MinorVersion"] : 0;
+ int patch = versionFields.ContainsKey("PatchVersion") ? (int)versionFields["PatchVersion"] : 0;
+ string tag = versionFields.ContainsKey("Build") ? (string)versionFields["Build"] : null;
+ return new SemanticVersionImpl(major, minor, patch, tag).ToString();
+ }
+ return new SemanticVersionImpl(versionObj.ToString()).ToString(); // SMAPI 2.0+
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get a case-insensitive dictionary matching the given JSON.</summary>
+ /// <param name="json">The JSON to parse.</param>
+ private IDictionary<string, object> Parse(string json)
+ {
+ IDictionary<string, object> MakeCaseInsensitive(IDictionary<string, object> dict)
+ {
+ foreach (var field in dict.ToArray())
+ {
+ if (field.Value is IDictionary<string, object> value)
+ dict[field.Key] = MakeCaseInsensitive(value);
+ }
+ return new Dictionary<string, object>(dict, StringComparer.InvariantCultureIgnoreCase);
+ }
+
+ IDictionary<string, object> data = (IDictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json);
+ return MakeCaseInsensitive(data);
+ }
+
+ /// <summary>Get whether a string is equal to another case-insensitively.</summary>
+ /// <param name="str">The string value.</param>
+ /// <param name="other">The string to compare with.</param>
+ private bool EqualsInvariant(string str, string other)
+ {
+ return str.Equals(other, StringComparison.InvariantCultureIgnoreCase);
+ }
+ }
+}
diff --git a/src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs b/src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs
new file mode 100644
index 00000000..64e31c29
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace StardewModdingAPI.ModBuildConfig.Framework
+{
+ /// <summary>A user error whose message can be displayed to the user.</summary>
+ internal class UserErrorException : Exception
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="message">The error message.</param>
+ public UserErrorException(string message)
+ : base(message) { }
+ }
+}
diff --git a/src/SMAPI.ModBuildConfig/Properties/AssemblyInfo.cs b/src/SMAPI.ModBuildConfig/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..6ef2d568
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/Properties/AssemblyInfo.cs
@@ -0,0 +1,9 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("StardewModdingAPI.ModBuildConfig")]
+[assembly: AssemblyDescription("")]
+[assembly: Guid("ea4f1e80-743f-4a1d-9757-ae66904a196a")]
+[assembly: ComVisible(false)]
+[assembly: AssemblyVersion("2.0.1.0")]
+[assembly: AssemblyFileVersion("2.0.1.0")]
diff --git a/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj
new file mode 100644
index 00000000..e04f09a7
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+ <ProjectGuid>{EA4F1E80-743F-4A1D-9757-AE66904A196A}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
+ <AssemblyName>StardewModdingAPI.ModBuildConfig</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.Build" />
+ <Reference Include="Microsoft.Build.Framework" />
+ <Reference Include="Microsoft.Build.Utilities.v4.0" />
+ <Reference Include="System" />
+ <Reference Include="System.IO.Compression" />
+ <Reference Include="System.Web.Extensions" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="DeployModTask.cs" />
+ <Compile Include="Framework\UserErrorException.cs" />
+ <Compile Include="Framework\ModFileManager.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="assets\nuget-icon.pdn" />
+ <None Include="build\smapi.targets">
+ <SubType>Designer</SubType>
+ </None>
+ <None Include="package.nuspec">
+ <SubType>Designer</SubType>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="assets\nuget-icon.png" />
+ </ItemGroup>
+ <Import Project="..\SMAPI.Common\StardewModdingAPI.Common.projitems" Label="Shared" />
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/src/SMAPI.ModBuildConfig/assets/nuget-icon.pdn b/src/SMAPI.ModBuildConfig/assets/nuget-icon.pdn
new file mode 100644
index 00000000..7bd5c0c5
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/assets/nuget-icon.pdn
Binary files differ
diff --git a/src/SMAPI.ModBuildConfig/assets/nuget-icon.png b/src/SMAPI.ModBuildConfig/assets/nuget-icon.png
new file mode 100644
index 00000000..611cdf88
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/assets/nuget-icon.png
Binary files differ
diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets
new file mode 100644
index 00000000..c0319e22
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/build/smapi.targets
@@ -0,0 +1,144 @@
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--*********************************************
+ ** Import build tasks
+ **********************************************-->
+ <UsingTask TaskName="DeployModTask" AssemblyFile="StardewModdingAPI.ModBuildConfig.dll" />
+
+ <!--*********************************************
+ ** Find the basic mod metadata
+ **********************************************-->
+ <!-- import developer's custom settings (if any) -->
+ <Import Condition="$(OS) != 'Windows_NT' AND Exists('$(HOME)\stardewvalley.targets')" Project="$(HOME)\stardewvalley.targets" />
+ <Import Condition="$(OS) == 'Windows_NT' AND Exists('$(USERPROFILE)\stardewvalley.targets')" Project="$(USERPROFILE)\stardewvalley.targets" />
+
+ <!-- set setting defaults -->
+ <PropertyGroup>
+ <!-- map legacy settings -->
+ <ModFolderName Condition="'$(ModFolderName)' == '' AND '$(DeployModFolderName)' != ''">$(DeployModFolderName)</ModFolderName>
+ <ModZipPath Condition="'$(ModZipPath)' == '' AND '$(DeployModZipTo)' != ''">$(DeployModZipTo)</ModZipPath>
+
+ <!-- set default settings -->
+ <ModFolderName Condition="'$(ModFolderName)' == ''">$(MSBuildProjectName)</ModFolderName>
+ <ModZipPath Condition="'$(ModZipPath)' == ''">$(TargetDir)</ModZipPath>
+ <EnableModDeploy Condition="'$(EnableModDeploy)' == ''">True</EnableModDeploy>
+ <EnableModZip Condition="'$(EnableModZip)' == ''">True</EnableModZip>
+ </PropertyGroup>
+
+ <!-- find platform + game path -->
+ <Choose>
+ <When Condition="$(OS) == 'Unix' OR $(OS) == 'OSX'">
+ <PropertyGroup>
+ <!-- Linux -->
+ <GamePath Condition="!Exists('$(GamePath)')">$(HOME)/GOG Games/Stardew Valley/game</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.local/share/Steam/steamapps/common/Stardew Valley</GamePath>
+
+ <!-- Mac (may be 'Unix' or 'OSX') -->
+ <GamePath Condition="!Exists('$(GamePath)')">/Applications/Stardew Valley.app/Contents/MacOS</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">$(HOME)/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS</GamePath>
+ </PropertyGroup>
+ </When>
+ <When Condition="$(OS) == 'Windows_NT'">
+ <PropertyGroup>
+ <GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\GOG.com\Games\1453375253', 'PATH', null, RegistryView.Registry32))</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150', 'InstallLocation', null, RegistryView.Registry64, RegistryView.Registry32))</GamePath>
+ </PropertyGroup>
+ </When>
+ </Choose>
+
+
+ <!--*********************************************
+ ** Inject the assembly references and debugging configuration
+ **********************************************-->
+ <Choose>
+ <When Condition="$(OS) == 'Windows_NT'">
+ <!-- references -->
+ <ItemGroup>
+ <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+ <Private>false</Private>
+ </Reference>
+ <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+ <Private>false</Private>
+ </Reference>
+ <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+ <Private>false</Private>
+ </Reference>
+ <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
+ <Private>false</Private>
+ </Reference>
+ <Reference Include="Stardew Valley">
+ <HintPath>$(GamePath)\Stardew Valley.exe</HintPath>
+ <Private>false</Private>
+ </Reference>
+ <Reference Include="StardewModdingAPI">
+ <HintPath>$(GamePath)\StardewModdingAPI.exe</HintPath>
+ <Private>false</Private>
+ </Reference>
+ <Reference Include="xTile, Version=2.0.4.0, Culture=neutral, processorArchitecture=x86">
+ <HintPath>$(GamePath)\xTile.dll</HintPath>
+ <Private>false</Private>
+ <SpecificVersion>False</SpecificVersion>
+ </Reference>
+ </ItemGroup>
+
+ <!-- launch game for debugging -->
+ <PropertyGroup>
+ <StartAction>Program</StartAction>
+ <StartProgram>$(GamePath)\StardewModdingAPI.exe</StartProgram>
+ <StartWorkingDirectory>$(GamePath)</StartWorkingDirectory>
+ </PropertyGroup>
+ </When>
+ <Otherwise>
+ <!-- references -->
+ <ItemGroup>
+ <Reference Include="MonoGame.Framework">
+ <HintPath>$(GamePath)\MonoGame.Framework.dll</HintPath>
+ <Private>false</Private>
+ <SpecificVersion>False</SpecificVersion>
+ </Reference>
+ <Reference Include="StardewValley">
+ <HintPath>$(GamePath)\StardewValley.exe</HintPath>
+ <Private>false</Private>
+ </Reference>
+ <Reference Include="StardewModdingAPI">
+ <HintPath>$(GamePath)\StardewModdingAPI.exe</HintPath>
+ <Private>false</Private>
+ </Reference>
+ <Reference Include="xTile">
+ <HintPath>$(GamePath)\xTile.dll</HintPath>
+ <Private>false</Private>
+ </Reference>
+ </ItemGroup>
+ </Otherwise>
+ </Choose>
+
+
+ <!--*********************************************
+ ** Deploy mod files & create release zip after build
+ **********************************************-->
+ <!-- if game path or OS is invalid, show one user-friendly error instead of a slew of reference errors -->
+ <Target Name="BeforeBuild">
+ <Error Condition="'$(OS)' != 'OSX' AND '$(OS)' != 'Unix' AND '$(OS)' != 'Windows_NT'" Text="The mod build package doesn't recognise OS type '$(OS)'." />
+
+ <Error Condition="!Exists('$(GamePath)')" Text="The mod build package can't find your game folder. You can specify where to find it; see details at https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#game-path." />
+ <Error Condition="'$(OS)' == 'Windows_NT' AND !Exists('$(GamePath)\Stardew Valley.exe')" Text="The mod build package found a a game folder at $(GamePath), but it doesn't contain the Stardew Valley.exe file. If this folder is invalid, delete it and the package will autodetect another game install path." />
+ <Error Condition="'$(OS)' != 'Windows_NT' AND !Exists('$(GamePath)\StardewValley.exe')" Text="The mod build package found a a game folder at $(GamePath), but it doesn't contain the StardewValley.exe file. If this folder is invalid, delete it and the package will autodetect another game install path." />
+ <Error Condition="!Exists('$(GamePath)\StardewModdingAPI.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain SMAPI. You need to install SMAPI before building the mod." />
+ </Target>
+
+ <!-- deploy mod files & create release zip -->
+ <Target Name="AfterBuild">
+ <DeployModTask
+ ModFolderName="$(ModFolderName)"
+ ModZipPath="$(ModZipPath)"
+
+ EnableModDeploy="$(EnableModDeploy)"
+ EnableModZip="$(EnableModZip)"
+
+ ProjectDir="$(ProjectDir)"
+ TargetDir="$(TargetDir)"
+ GameDir="$(GamePath)"
+ />
+ </Target>
+</Project>
diff --git a/src/SMAPI.ModBuildConfig/package.nuspec b/src/SMAPI.ModBuildConfig/package.nuspec
new file mode 100644
index 00000000..b1b228de
--- /dev/null
+++ b/src/SMAPI.ModBuildConfig/package.nuspec
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
+ <metadata>
+ <id>Pathoschild.Stardew.ModBuildConfig</id>
+ <version>2.0.1</version>
+ <title>Build package for SMAPI mods</title>
+ <authors>Pathoschild</authors>
+ <owners>Pathoschild</owners>
+ <requireLicenseAcceptance>false</requireLicenseAcceptance>
+ <licenseUrl>https://github.com/Pathoschild/SMAPI/blob/develop/LICENSE.txt</licenseUrl>
+ <projectUrl>https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#readme</projectUrl>
+ <iconUrl>https://raw.githubusercontent.com/Pathoschild/SMAPI/develop/src/SMAPI.ModBuildConfig/assets/nuget-icon.png</iconUrl>
+ <description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods.</description>
+ <releaseNotes>
+ 2.0:
+ - Added: mods are now copied into the `Mods` folder automatically (configurable).
+ - Added: release zips are now created automatically in your build output folder (configurable).
+ - Added: mod deploy and release zips now exclude Json.NET automatically, since it's provided by SMAPI.
+ - Added mod's version to release zip filename.
+ - Improved errors to simplify troubleshooting.
+ - Fixed release zip not having a mod folder.
+ - Fixed release zip failing if mod name contains characters that aren't valid in a filename.
+
+ 2.0.1:
+ - Fixed mod deploy failing to create subfolders if they don't already exist.
+ </releaseNotes>
+ </metadata>
+ <files>
+ <file src="build/smapi.targets" target="build/Pathoschild.Stardew.ModBuildConfig.targets" />
+ <file src="bin/StardewModdingAPI.ModBuildConfig.dll" target="build/StardewModdingAPI.ModBuildConfig.dll" />
+ </files>
+</package>