summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/common.targets1
-rw-r--r--build/prepare-install-package.targets123
-rw-r--r--docs/release-notes.md2
-rw-r--r--docs/technical-docs.md53
-rw-r--r--src/SMAPI.Installer/Framework/InstallerPaths.cs14
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs88
-rw-r--r--src/SMAPI.Installer/Program.cs32
-rw-r--r--src/SMAPI.Installer/README.txt13
-rw-r--r--src/SMAPI.Installer/StardewModdingAPI.Installer.csproj2
-rw-r--r--src/SMAPI.Installer/unix-install.sh2
-rw-r--r--src/SMAPI.Installer/windows-install.bat2
11 files changed, 203 insertions, 129 deletions
diff --git a/build/common.targets b/build/common.targets
index 320b3e00..d9ad89f4 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -108,7 +108,6 @@
<Copy SourceFiles="$(TargetDir)\$(TargetName).config.json" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).metadata.json" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
- <Copy SourceFiles="$(TargetDir)\0Harmony.pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
</Target>
diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets
index 09114aee..dd15fb52 100644
--- a/build/prepare-install-package.targets
+++ b/build/prepare-install-package.targets
@@ -10,9 +10,10 @@
<CompiledRootPath>$(SolutionDir)\..\bin\$(Configuration)</CompiledRootPath>
<CompiledSmapiPath>$(CompiledRootPath)\SMAPI</CompiledSmapiPath>
<CompiledToolkitPath>$(CompiledRootPath)\SMAPI.Toolkit\net4.5</CompiledToolkitPath>
- <PackagePath>$(SolutionDir)\..\bin\Packaged</PackagePath>
- <PackageInternalPath Condition="$(OS) == 'Windows_NT'">$(PackagePath)\internal\Windows</PackageInternalPath>
- <PackageInternalPath Condition="$(OS) != 'Windows_NT'">$(PackagePath)\internal\Mono</PackageInternalPath>
+ <PackagePath>$(SolutionDir)\..\bin\SMAPI installer</PackagePath>
+ <PackageDevPath>$(SolutionDir)\..\bin\SMAPI installer for developers</PackageDevPath>
+ <PackageBundlePlatform>windows</PackageBundlePlatform>
+ <PackageBundlePlatform Condition="$(OS) != 'Windows_NT'">mono</PackageBundlePlatform>
</PropertyGroup>
<ItemGroup>
<CompiledMods Include="$(SolutionDir)\..\bin\$(Configuration)\Mods\**\*.*" />
@@ -20,40 +21,102 @@
<!-- reset package directory -->
<RemoveDir Directories="$(PackagePath)" />
+ <RemoveDir Directories="$(PackageDevPath)" />
<!-- copy installer files -->
- <Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(PackageInternalPath)\install.exe" />
- <Copy SourceFiles="$(TargetDir)\unix-launcher.sh" DestinationFiles="$(PackageInternalPath)\StardewModdingAPI" />
- <Copy SourceFiles="$(TargetDir)\README.txt" DestinationFiles="$(PackagePath)\README.txt" />
<Copy SourceFiles="$(TargetDir)\unix-install.sh" DestinationFiles="$(PackagePath)\install on Linux.sh" />
<Copy SourceFiles="$(TargetDir)\unix-install.sh" DestinationFiles="$(PackagePath)\install on Mac.command" />
<Copy SourceFiles="$(TargetDir)\windows-install.bat" DestinationFiles="$(PackagePath)\install on Windows.bat" />
+ <Copy SourceFiles="$(TargetDir)\README.txt" DestinationFiles="$(PackagePath)\README.txt" />
+ <Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(PackagePath)\internal\install.exe" />
+ <Copy SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackagePath)\internal\install.exe.config" />
- <!--copy *.exe.config files (needed to avoid security errors when loading DLLs)-->
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackageInternalPath)\install.exe.config" />
- <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackageInternalPath)\StardewModdingAPI.exe.config" />
+ <!--copy bundle files-->
+ <Copy SourceFiles="$(TargetDir)\unix-launcher.sh" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(PackagePath)\bundle" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.pdb" DestinationFolder="$(PackagePath)\bundle" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.xml" DestinationFolder="$(PackagePath)\bundle" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(PackagePath)\bundle" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.metadata.json" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="@(CompiledMods)" DestinationFolder="$(PackagePath)\bundle\Mods\%(RecursiveDir)" />
+ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI.exe.config" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
- <!-- copy SMAPI files -->
- <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(PackageInternalPath)" />
- <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.pdb" DestinationFolder="$(PackageInternalPath)" />
- <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.xml" DestinationFolder="$(PackageInternalPath)" />
- <Copy SourceFiles="$(CompiledSmapiPath)\steam_appid.txt" DestinationFolder="$(PackageInternalPath)" />
- <Copy SourceFiles="$(CompiledSmapiPath)\0Harmony.dll" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledSmapiPath)\0Harmony.pdb" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.metadata.json" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.dll" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.pdb" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.xml" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy SourceFiles="@(CompiledMods)" DestinationFolder="$(PackageInternalPath)\Mods\%(RecursiveDir)" />
+ <!-- fix Linux/Mac permissions -->
+ <Exec Condition="$(OS) != 'Windows_NT'" Command="chmod 755 &quot;$(PackagePath)\install on Linux.sh&quot;" />
+ <Exec Condition="$(OS) != 'Windows_NT'" Command="chmod 755 &quot;$(PackagePath)\install on Mac.command&quot;" />
- <!--copy SMAPI files for Linux/Mac only -->
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
- <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(PackageInternalPath)\smapi-internal" />
+ <!-- finalise 'for developers' installer -->
+ <ItemGroup>
+ <PackageFiles Include="$(PackagePath)\**\*.*" />
+ </ItemGroup>
+ <Copy SourceFiles="@(PackageFiles)" DestinationFolder="$(PackageDevPath)\%(RecursiveDir)" />
+ <ZipDirectory FromDirPath="$(PackageDevPath)\bundle" ToFilePath="$(PackageDevPath)\internal\$(PackageBundlePlatform).dat" />
+ <RemoveDir Directories="$(PackageDevPath)\bundle" />
+
+ <!-- finalise normal installer -->
+ <ReplaceFileText FilePath="$(PackagePath)\bundle\smapi-internal\StardewModdingAPI.config.json" Search="&quot;DeveloperMode&quot;: true" Replace="&quot;DeveloperMode&quot;: false" />
+ <ZipDirectory FromDirPath="$(PackagePath)\bundle" ToFilePath="$(PackagePath)\internal\$(PackageBundlePlatform).dat" />
+ <RemoveDir Directories="$(PackagePath)\bundle" />
</Target>
+
+ <!-- Create a zip file with the contents of a given folder path. Derived from https://stackoverflow.com/a/38127938/262123. -->
+ <UsingTask TaskName="ZipDirectory" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
+ <ParameterGroup>
+ <FromDirPath ParameterType="System.String" Required="true" />
+ <ToFilePath ParameterType="System.String" Required="true" />
+ </ParameterGroup>
+ <Task>
+ <Reference Include="System.IO.Compression.FileSystem" />
+ <Using Namespace="System.IO.Compression" />
+ <Code Type="Fragment" Language="cs">
+ <![CDATA[
+ try
+ {
+ ZipFile.CreateFromDirectory(FromDirPath, ToFilePath);
+ return true;
+ }
+ catch(Exception ex)
+ {
+ Log.LogErrorFromException(ex);
+ return false;
+ }
+ ]]>
+ </Code>
+ </Task>
+ </UsingTask>
+
+ <!-- Replace text in a file based on a regex pattern. Derived from https://stackoverflow.com/a/22571621/262123. -->
+ <UsingTask TaskName="ReplaceFileText" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
+ <ParameterGroup>
+ <FilePath ParameterType="System.String" Required="true" />
+ <Search ParameterType="System.String" Required="true" />
+ <Replace ParameterType="System.String" Required="true" />
+ </ParameterGroup>
+ <Task>
+ <Reference Include="System.Core" />
+ <Using Namespace="System" />
+ <Using Namespace="System.IO" />
+ <Using Namespace="System.Text.RegularExpressions" />
+ <Code Type="Fragment" Language="cs">
+ <![CDATA[
+ File.WriteAllText(
+ FilePath,
+ Regex.Replace(File.ReadAllText(FilePath), Search, Replace)
+ );
+ ]]>
+ </Code>
+ </Task>
+ </UsingTask>
</Project>
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 5762c623..d39e6f3e 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -5,6 +5,7 @@
* Reorganised SMAPI files:
* Most SMAPI files are now tucked into a `smapi-internal` subfolder.
* Save backups are now in a `save-backups` subfolder, so they're easier to access. Note that previous backups will be deleted when you update.
+ * Simplified the installer files to avoid confusion.
* Added support for organising mods:
* You can now group mods into subfolders to organise them.
* You can now mark a mod folder ignored by starting the name with a dot (like `.disabled mods`).
@@ -18,6 +19,7 @@
* Fixed friendly error no longer shown when SMAPI isn't run from the game folder.
* Fixed some Windows install paths not detected.
* Fixed installer duplicating bundled mods if you moved them after the last install.
+ * Fixed installer allowing custom mods to be bundled with the install.
* Fixed translation issues not shown as warnings.
* Fixed dependencies not correctly enforced if the dependency is installed but failed to load.
* Fixed some errors logged as SMAPI instead of the affected mod.
diff --git a/docs/technical-docs.md b/docs/technical-docs.md
index 4a8f45f1..83e4ea2b 100644
--- a/docs/technical-docs.md
+++ b/docs/technical-docs.md
@@ -44,7 +44,7 @@ executed. This doesn't work in MonoDevelop on Linux, unfortunately.
### Preparing a release
To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms. See
-[crossplatforming info](https://stardewvalleywiki.com/Modding:Creating_a_SMAPI_mod#Test_on_all_platforms)
+[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms)
on the wiki for the first-time setup.
1. Update the version number in `GlobalAssemblyInfo.cs` and `Constants::Version`. Make sure you use a
@@ -57,47 +57,16 @@ on the wiki for the first-time setup.
release | `<version>` | `3.0`
2. In Windows:
- 1. Rebuild the solution in _Release_ mode.
- 2. Rename `bin/Packaged` to `SMAPI <version>` (e.g. `SMAPI 3.0`).
- 2. Transfer the `SMAPI <version>` folder to Linux or Mac.
- _This adds the installer executable and Windows files. We'll do the rest in Linux or Mac,
- since we need to set Unix file permissions that Windows won't save._
-
-2. In Linux or Mac:
- 1. Rebuild the solution in _Release_ mode.
- 2. Copy `bin/internal/Packaged/Mono` into the `SMAPI <version>` folder.
- 3. If you did everything right so far, you should have a folder like this:
-
- ```
- SMAPI 3.0 installer/
- install on Linux.sh
- install on Mac.command
- install on Windows.exe
- README.txt
- internal/
- Mono/
- Mods/*
- smapi-internal/*
- StardewModdingAPI
- StardewModdingAPI.exe
- StardewModdingAPI.pdb
- StardewModdingAPI.xml
- steam_appid.txt
- Windows/
- Mods/*
- smapi-internal/*
- StardewModdingAPI.exe
- StardewModdingAPI.pdb
- StardewModdingAPI.xml
- steam_appid.txt
- ```
- 4. Open a terminal in the `SMAPI <version>` folder and run `chmod 755 internal/Mono/StardewModdingAPI`.
- 5. Copy & paste the `SMAPI <version>` folder as `SMAPI <version> for developers`.
- 6. In the `SMAPI <version>` folder...
- * edit `internal/Mono/StardewModdingAPI.config.json` and
- `internal/Windows/StardewModdingAPI.config.json` to disable developer mode;
- * delete `internal/Windows/StardewModdingAPI.xml`.
- 7. Compress the two folders into `SMAPI <version>.zip` and `SMAPI <version> for developers.zip`.
+ 1. Rebuild the solution in Release mode.
+ 2. Copy `windows.dat` from `bin/SMAPI installer` and `bin/SMAPI installer for developers` to
+ Linux/Mac.
+
+3. In Linux/Mac:
+ 1. Rebuild the solution in Release mode.
+ 2. Add the `windows.dat` files to the `bin/SMAPI installer` and
+ `bin/SMAPI installer for developers` folders.
+ 3. Rename the folders to `SMAPI <version> installer` and `SMAPI <version> installer for developers`.
+ 4. Zip the two folders.
## Customisation
### Configuration file
diff --git a/src/SMAPI.Installer/Framework/InstallerPaths.cs b/src/SMAPI.Installer/Framework/InstallerPaths.cs
index 65e7699b..e5396018 100644
--- a/src/SMAPI.Installer/Framework/InstallerPaths.cs
+++ b/src/SMAPI.Installer/Framework/InstallerPaths.cs
@@ -8,8 +8,8 @@ namespace StardewModdingAPI.Installer.Framework
/*********
** Accessors
*********/
- /// <summary>The directory containing the installer files for the current platform.</summary>
- public DirectoryInfo PackageDir { get; }
+ /// <summary>The directory path containing the files to copy into the game folder.</summary>
+ public DirectoryInfo BundleDir { get; }
/// <summary>The directory containing the installed game.</summary>
public DirectoryInfo GameDir { get; }
@@ -17,8 +17,8 @@ namespace StardewModdingAPI.Installer.Framework
/// <summary>The directory into which to install mods.</summary>
public DirectoryInfo ModsDir { get; }
- /// <summary>The full path to the directory containing the installer files for the current platform.</summary>
- public string PackagePath => this.PackageDir.FullName;
+ /// <summary>The full path to directory path containing the files to copy into the game folder.</summary>
+ public string BundlePath => this.BundleDir.FullName;
/// <summary>The full path to the directory containing the installed game.</summary>
public string GamePath => this.GameDir.FullName;
@@ -46,12 +46,12 @@ namespace StardewModdingAPI.Installer.Framework
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="packageDir">The directory path containing the installer files for the current platform.</param>
+ /// <param name="bundleDir">The directory path containing the files to copy into the game folder.</param>
/// <param name="gameDir">The directory path for the installed game.</param>
/// <param name="gameExecutableName">The name of the game's executable file for the current platform.</param>
- public InstallerPaths(DirectoryInfo packageDir, DirectoryInfo gameDir, string gameExecutableName)
+ public InstallerPaths(DirectoryInfo bundleDir, DirectoryInfo gameDir, string gameExecutableName)
{
- this.PackageDir = packageDir;
+ this.BundleDir = bundleDir;
this.GameDir = gameDir;
this.ModsDir = new DirectoryInfo(Path.Combine(gameDir.FullName, "Mods"));
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index 85882283..d5866c74 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Reflection;
using System.Threading;
using Microsoft.Win32;
using StardewModdingApi.Installer.Enums;
@@ -22,12 +22,19 @@ namespace StardewModdingApi.Installer
/*********
** Properties
*********/
- /// <summary>The name of the installer file in the package.</summary>
- private readonly string InstallerFileName = "install.exe";
+ /// <summary>The absolute path to the directory containing the files to copy into the game folder.</summary>
+ private readonly string BundlePath;
/// <summary>The <see cref="Environment.OSVersion"/> value that represents Windows 7.</summary>
private readonly Version Windows7Version = new Version(6, 1);
+ /// <summary>The mod IDs which the installer should allow as bundled mods.</summary>
+ private readonly string[] BundledModIDs = new[]
+ {
+ "SMAPI.SaveBackup",
+ "SMAPI.ConsoleCommands"
+ };
+
/// <summary>The default file paths where Stardew Valley can be installed.</summary>
/// <param name="platform">The target platform.</param>
/// <remarks>Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. </remarks>
@@ -146,8 +153,10 @@ namespace StardewModdingApi.Installer
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- public InteractiveInstaller()
+ /// <param name="bundlePath">The absolute path to the directory containing the files to copy into the game folder.</param>
+ public InteractiveInstaller(string bundlePath)
{
+ this.BundlePath = bundlePath;
this.ConsoleWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.AutoDetect);
}
@@ -322,8 +331,8 @@ namespace StardewModdingApi.Installer
}
// get folders
- DirectoryInfo packageDir = new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
- paths = new InstallerPaths(packageDir, installDir, EnvironmentUtility.GetExecutableName(platform));
+ DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath);
+ paths = new InstallerPaths(bundleDir, installDir, EnvironmentUtility.GetExecutableName(platform));
}
Console.Clear();
@@ -331,23 +340,11 @@ namespace StardewModdingApi.Installer
/*********
** Step 4: validate assumptions
*********/
+ if (!File.Exists(paths.ExecutablePath))
{
- if (!paths.PackageDir.Exists)
- {
- this.PrintError(platform == Platform.Windows && paths.PackagePath.Contains(Path.GetTempPath()) && paths.PackagePath.Contains(".zip")
- ? "The installer is missing some files. It looks like you're running the installer from inside the downloaded zip; make sure you unzip the downloaded file first, then run the installer from the unzipped folder."
- : $"The 'internal/{paths.PackageDir.Name}' package folder is missing (should be at {paths.PackagePath})."
- );
- Console.ReadLine();
- return;
- }
-
- if (!File.Exists(paths.ExecutablePath))
- {
- this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
- Console.ReadLine();
- return;
- }
+ this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
+ Console.ReadLine();
+ return;
}
@@ -439,11 +436,8 @@ namespace StardewModdingApi.Installer
{
// copy SMAPI files to game dir
this.PrintDebug("Adding SMAPI files...");
- foreach (FileSystemInfo sourceEntry in paths.PackageDir.EnumerateFileSystemInfos().Where(this.ShouldCopy))
+ foreach (FileSystemInfo sourceEntry in paths.BundleDir.EnumerateFileSystemInfos().Where(this.ShouldCopy))
{
- if (sourceEntry.Name.StartsWith(this.InstallerFileName)) // e.g. install.exe or install.exe.config
- continue;
-
this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name));
this.RecursiveCopy(sourceEntry, paths.GameDir);
}
@@ -452,6 +446,8 @@ namespace StardewModdingApi.Installer
if (platform.IsMono())
{
this.PrintDebug("Safely replacing game launcher...");
+
+ // back up & remove current launcher
if (File.Exists(paths.UnixLauncherPath))
{
if (!File.Exists(paths.UnixBackupLauncherPath))
@@ -460,7 +456,20 @@ namespace StardewModdingApi.Installer
this.InteractivelyDelete(paths.UnixLauncherPath);
}
+ // add new launcher
File.Move(paths.UnixSmapiLauncherPath, paths.UnixLauncherPath);
+
+ // mark file executable
+ // (MSBuild doesn't keep permission flags for files zipped in a build task.)
+ new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = "chmod",
+ Arguments = $"755 \"{paths.UnixLauncherPath}\"",
+ CreateNoWindow = true
+ }
+ }.Start();
}
// create mods directory (if needed)
@@ -471,14 +480,14 @@ namespace StardewModdingApi.Installer
}
// add or replace bundled mods
- DirectoryInfo packagedModsDir = new DirectoryInfo(Path.Combine(paths.PackageDir.FullName, "Mods"));
- if (packagedModsDir.Exists && packagedModsDir.EnumerateDirectories().Any())
+ DirectoryInfo bundledModsDir = new DirectoryInfo(Path.Combine(paths.BundlePath, "Mods"));
+ if (bundledModsDir.Exists && bundledModsDir.EnumerateDirectories().Any())
{
this.PrintDebug("Adding bundled mods...");
ModToolkit toolkit = new ModToolkit();
ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath).ToArray();
- foreach (ModFolder sourceMod in toolkit.GetModFolders(packagedModsDir.FullName))
+ foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName))
{
// validate source mod
if (sourceMod.Manifest == null)
@@ -486,6 +495,11 @@ namespace StardewModdingApi.Installer
this.PrintWarning($" ignored invalid bundled mod {sourceMod.DisplayName}: {sourceMod.ManifestParseError}");
continue;
}
+ if (!this.BundledModIDs.Contains(sourceMod.Manifest.UniqueID))
+ {
+ this.PrintWarning($" ignored unknown '{sourceMod.DisplayName}' mod in the installer folder. To add mods, put them here instead: {paths.ModsPath}");
+ continue;
+ }
// find target folder
ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase) == true);
@@ -496,14 +510,12 @@ namespace StardewModdingApi.Installer
: $" adding {sourceMod.Manifest.Name} to {Path.Combine(paths.ModsDir.Name, PathUtilities.GetRelativePath(paths.ModsPath, targetFolder.FullName))}..."
);
- // (re)create target folder
+ // remove existing folder
if (targetFolder.Exists)
this.InteractivelyDelete(targetFolder.FullName);
- targetFolder.Create();
// copy files
- foreach (FileInfo sourceFile in sourceMod.Directory.EnumerateFiles().Where(this.ShouldCopy))
- sourceFile.CopyTo(Path.Combine(targetFolder.FullName, sourceFile.Name));
+ this.RecursiveCopy(sourceMod.Directory, paths.ModsDir, filter: this.ShouldCopy);
}
}
@@ -517,7 +529,7 @@ namespace StardewModdingApi.Installer
}
// remove obsolete appdata mods
- this.InteractivelyRemoveAppDataMods(paths.ModsDir, packagedModsDir);
+ this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir);
}
}
Console.WriteLine();
@@ -686,8 +698,12 @@ namespace StardewModdingApi.Installer
/// <summary>Recursively copy a directory or file.</summary>
/// <param name="source">The file or folder to copy.</param>
/// <param name="targetFolder">The folder to copy into.</param>
- private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder)
+ /// <param name="filter">A filter which matches directories and files to copy, or <c>null</c> to match all.</param>
+ private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func<FileSystemInfo, bool> filter = null)
{
+ if (filter != null && !filter(source))
+ return;
+
if (!targetFolder.Exists)
targetFolder.Create();
@@ -700,7 +716,7 @@ namespace StardewModdingApi.Installer
case DirectoryInfo sourceDir:
DirectoryInfo targetSubfolder = new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name));
foreach (var entry in sourceDir.EnumerateFileSystemInfos())
- this.RecursiveCopy(entry, targetSubfolder);
+ this.RecursiveCopy(entry, targetSubfolder, filter);
break;
default:
diff --git a/src/SMAPI.Installer/Program.cs b/src/SMAPI.Installer/Program.cs
index ad5cf47f..c56c9f80 100644
--- a/src/SMAPI.Installer/Program.cs
+++ b/src/SMAPI.Installer/Program.cs
@@ -1,7 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.IO.Compression;
using System.Reflection;
+using StardewModdingAPI.Internal;
+using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingApi.Installer
{
@@ -11,9 +14,15 @@ namespace StardewModdingApi.Installer
/*********
** Properties
*********/
- /// <summary>The absolute path to search for referenced assemblies.</summary>
+ /// <summary>The absolute path of the installer folder.</summary>
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "The assembly location is never null in this context.")]
- private static readonly string DllSearchPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "smapi-internal");
+ private static readonly string InstallerPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+
+ /// <summary>The absolute path of the folder containing the unzipped installer files.</summary>
+ private static readonly string ExtractedBundlePath = Path.Combine(Path.GetTempPath(), $"SMAPI-installer-{Guid.NewGuid():N}");
+
+ /// <summary>The absolute path for referenced assemblies.</summary>
+ private static readonly string InternalFilesPath = Path.Combine(Program.ExtractedBundlePath, "smapi-internal");
/*********
** Public methods
@@ -22,11 +31,26 @@ namespace StardewModdingApi.Installer
/// <param name="args">The command line arguments.</param>
public static void Main(string[] args)
{
+ // find install bundle
+ PlatformID platform = Environment.OSVersion.Platform;
+ FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, $"{(platform == PlatformID.Win32NT ? "windows" : "mono")}.dat"));
+ if (!zipFile.Exists)
+ {
+ Console.WriteLine($"Oops! Some of the installer files are missing; try redownloading the installer. (Missing file: {zipFile.FullName})");
+ Console.ReadLine();
+ return;
+ }
+
+ // unzip bundle into temp folder
+ DirectoryInfo bundleDir = new DirectoryInfo(Program.ExtractedBundlePath);
+ Console.WriteLine("Extracting install files...");
+ ZipFile.ExtractToDirectory(zipFile.FullName, bundleDir.FullName);
+
// set up assembly resolution
AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve;
// launch installer
- var installer = new InteractiveInstaller();
+ var installer = new InteractiveInstaller(bundleDir.FullName);
installer.Run(args);
}
@@ -41,7 +65,7 @@ namespace StardewModdingApi.Installer
try
{
AssemblyName name = new AssemblyName(e.Name);
- foreach (FileInfo dll in new DirectoryInfo(Program.DllSearchPath).EnumerateFiles("*.dll"))
+ foreach (FileInfo dll in new DirectoryInfo(Program.InternalFilesPath).EnumerateFiles("*.dll"))
{
if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.InvariantCultureIgnoreCase))
return Assembly.LoadFrom(dll.FullName);
diff --git a/src/SMAPI.Installer/README.txt b/src/SMAPI.Installer/README.txt
index 88fe4a03..abff9938 100644
--- a/src/SMAPI.Installer/README.txt
+++ b/src/SMAPI.Installer/README.txt
@@ -16,7 +16,7 @@ SMAPI lets you run Stardew Valley with mods. Don't forget to download mods separ
Player's guide
--------------------------------
-See https://stardewvalleywiki.com/Modding:Player_Guide
+See https://stardewvalleywiki.com/Modding:Player_Guide for help installing SMAPI, adding mods, etc.
Manual install
@@ -24,12 +24,11 @@ Manual install
THIS IS NOT RECOMMENDED FOR MOST PLAYERS. See instructions above instead.
If you really want to install SMAPI manually, here's how.
-1. Download the latest version of SMAPI: https://github.com/Pathoschild/SMAPI/releases
-2. Unzip the .zip file somewhere (not in your game folder).
-3. Copy the files from the "internal/Windows" folder (on Windows) or "internal/Mono" folder (on
- Linux/Mac) into your game folder. The `StardewModdingAPI.exe` file should be right next to the
- game's executable.
-4.
+1. Unzip "internal/windows.dat" (on Windows) or "internal/mono.dat" (on Linux/Mac). You can change
+ '.dat' to '.zip', it's just a normal zip file renamed to prevent confusion.
+2. Copy the files from the folder you just unzipped into your game folder. The
+ `StardewModdingAPI.exe` file should be right next to the game's executable.
+3.
- Windows only: if you use Steam, see the install guide above to enable achievements and
overlay. Otherwise, just run StardewModdingAPI.exe in your game folder to play with mods.
diff --git a/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj b/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj
index 22c55af7..8000e4e7 100644
--- a/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj
+++ b/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj
@@ -34,6 +34,8 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
+ <Reference Include="System.IO.Compression" />
+ <Reference Include="System.IO.Compression.FileSystem" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
diff --git a/src/SMAPI.Installer/unix-install.sh b/src/SMAPI.Installer/unix-install.sh
index 8379ed87..57e48f4b 100644
--- a/src/SMAPI.Installer/unix-install.sh
+++ b/src/SMAPI.Installer/unix-install.sh
@@ -14,7 +14,7 @@ fi
# validate Mono & run installer
if $COMMAND mono >/dev/null 2>&1; then
- mono internal/Mono/install.exe
+ mono internal/install.exe
else
echo "Oops! Looks like Mono isn't installed. Please install Mono from https://mono-project.com, reboot, and run this installer again."
read
diff --git a/src/SMAPI.Installer/windows-install.bat b/src/SMAPI.Installer/windows-install.bat
index 716c7789..1ebec989 100644
--- a/src/SMAPI.Installer/windows-install.bat
+++ b/src/SMAPI.Installer/windows-install.bat
@@ -1 +1 @@
-START /WAIT /B internal/Windows/install.exe
+START /WAIT /B internal/install.exe