public static GmcManager PrepareGmcExecutor( ProfilePresetType profileType, string absolutePathToProject, string absolutePathToGothic2Root, string configurationFile) { var userGmcConfig = UserGmcConfigurationLoader.Load(absolutePathToProject, configurationFile); var gmcFolderPath = Path.Combine(absolutePathToGothic2Root, ".gmc"); var gothicFolder = GothicFolder.CreateFromPath(absolutePathToGothic2Root); var gmcFolder = GmcFolder.CreateFromPath(gmcFolderPath); var modFolder = ModFolder.CreateFromPath(absolutePathToProject); var profileResponse = ProfileDefinitionLoader.Load(profileType, gothicFolder, gmcFolder, modFolder, userGmcConfig); return(GmcManager.Create(profileResponse)); }
public VFS(AbsolutePath mo2Path, string profile, AbsolutePath[] ignoreFolders) { IgnoredFolders = ignoreFolders; ModOrganizer2Path = mo2Path; MO2Ini = ModOrganizer2Path.Combine("ModOrganizer.ini").LoadIniFile(); ModFolder = mo2Path.Combine("mods"); Mods = ModFolder.EnumerateDirectories(false) .Where(f => !ignoreFolders.Any(f.InFolder)) .Select(m => new Mod(m.FileName.ToString(), this)) .ToDictionary(m => m.Name); ProfileFolder = mo2Path.Combine("profiles", profile); ModlistDefinition = LoadModList(ProfileFolder.Combine("modlist.txt")); ModList = ModlistDefinition.Where(m => m.Status == ModStatus.Enabled) .Select(m => (m.Status, Mods[m.Name])) .ToArray(); AppliedList = ModList.Select(m => m.Mod).SelectMany(m => m.Files).ToLookup(m => m.Path); PluginListDefinition = LoadPluginList(ProfileFolder.Combine("plugins.txt")); Plugins = PluginListDefinition.Where(p => p.Status == PluginStatus.Enabled) .Select(p => AppliedList[(RelativePath)p.Name].First()) .ToArray(); AppliedBSAs = Plugins.SelectMany(e => { var name = e.Path.FileNameWithoutExtension.ToString(); return(new[] { (RelativePath)(name + ".bsa"), (RelativePath)(name + " - Textures.bsa") }); }).Select(bsa => AppliedList[bsa]) .Where(bsa => bsa.Any()) .Select(bsa => bsa.First()) .Select(bsa => (bsa, BSADispatch.OpenRead(bsa.AbsolutePath).GetAwaiter().GetResult())) .ToArray(); AppliedBSAFiles = AppliedBSAs .SelectMany(bsa => bsa.BSA.Files .Select(f => new BSAModFile(bsa.Mod, f))).ToArray(); AllAppliedFiles = AppliedBSAFiles .Concat(ModList.Select(m => m.Mod).SelectMany(m => m.Files).Cast <IModFile>()) .ToLookup(f => f.Path); }
//Updates Mod Folders (Keeps same mods present) private void updateModFolders() { for (int i = 0; i < AllMods.Count; i++) { ModFolder updatedMod; updatedMod = new ModFolder(); updatedMod = AllMods.ElementAt(i); //Since this is what should be changing updatedMod.fileVersions.Clear(); updatedMod.fileCount = isValidModFolder(updatedMod.filePath, ref updatedMod.fileVersions, ref updatedMod.packagePaths); updatedMod.versionString = buildVersionString(ref updatedMod.fileVersions); AllMods[i] = updatedMod; } updateCheckBoxList(); }
//Grabs Mod Folders (Starts from scratch) private void getModFolders(string modDirectory) { AllMods.Clear(); //Grabs all possible Mod Folders List <string> possFolders = new List <string>(Directory.GetDirectories(betterFolderBrowser1.SelectedPath)); for (int i = 0; i < possFolders.Count; i++) { //Make a new Mod ModFolder NewMod; NewMod = new ModFolder(); //This is the path to the Mod Folder NewMod.filePath = Path.GetFullPath(possFolders.ElementAt(i).TrimEnd(Path.DirectorySeparatorChar)); //This is the name we refer to the Mod by in the checkboxes NewMod.ModName = Path.GetFileName(NewMod.filePath); //Initialize file versions list NewMod.fileVersions = new List <int>(); //Initialize package paths list NewMod.packagePaths = new List <string>(); //Update accordingly NewMod.fileCount = isValidModFolder(NewMod.filePath, ref NewMod.fileVersions, ref NewMod.packagePaths); //This is now a valid mod. if (NewMod.fileCount > 0) { NewMod.versionString = buildVersionString(ref NewMod.fileVersions); AllMods.Add(NewMod); } } //Refresh Checkbox with new Mods setCheckBoxList(); }
//Handles setting up the Mod Folders upon selectin a Mod Directory private void buttonModDirectory_Click(object sender, EventArgs e) { if (betterFolderBrowser1 == null) { betterFolderBrowser1 = new BetterFolderBrowser(); betterFolderBrowser1.Title = "Select Mod Folder Directory"; //betterFolderBrowser1.RootFolder = "C:\\"; } if (betterFolderBrowser1.ShowDialog(this.FindForm()) == DialogResult.OK) { //Refresh Mods and CheckBoxList getModFolders(betterFolderBrowser1.SelectedPath); //Maybe the user selected a direct Mod folder instead of the directory, lets see. if (AllMods.Count <= 0) { ModFolder NewMod; NewMod = new ModFolder(); NewMod.filePath = betterFolderBrowser1.SelectedPath; NewMod.ModName = Path.GetFileName(NewMod.filePath); NewMod.fileVersions = new List <int>(); NewMod.packagePaths = new List <string>(); NewMod.fileCount = isValidModFolder(NewMod.filePath, ref NewMod.fileVersions, ref NewMod.packagePaths); //This is now a valid mod. if (NewMod.fileCount > 0) { NewMod.versionString = buildVersionString(ref NewMod.fileVersions); AllMods.Add(NewMod); setCheckBoxList(); } } } }
//Performs actual operation of changing package versions, returns # of files altered private int changeModPackageVersion(ModFolder Mod, ushort NewVersion, bool changeAllVersions, ushort OldVersion) { int modifiedFileCount = 0; for (int i = 0; i < Mod.packagePaths.Count; i++) { try { //First double check this file should be written to BinaryReader binReader = new BinaryReader(File.Open(Mod.packagePaths.ElementAt(i), FileMode.Open)); binReader.ReadInt32(); //Skip a 32 bit integer size ushort fileVersion = binReader.ReadUInt16(); binReader.Close(); //Make sure we aren't writing the exact same version and that this version is one intended to be written to if (fileVersion != NewVersion && (changeAllVersions || fileVersion == OldVersion)) { BinaryWriter binWriter = new BinaryWriter(File.Open(Mod.packagePaths.ElementAt(i), FileMode.Open)); binWriter.Seek(4, SeekOrigin.Begin); //Skip a 32 bit integer size binWriter.Write(NewVersion); //Write the 16 bit unsigned integer package version number binWriter.Close(); modifiedFileCount++; } } catch (Exception e) { Debug.WriteLine("Exception {0}", e); } } return(modifiedFileCount); }
/// <summary>Run the install or uninstall script.</summary> /// <param name="args">The command line arguments.</param> /// <remarks> /// Initialisation flow: /// 1. Collect information (mainly OS and install path) and validate it. /// 2. Ask the user whether to install or uninstall. /// /// Uninstall logic: /// 1. On Linux/Mac: if a backup of the launcher exists, delete the launcher and restore the backup. /// 2. Delete all files and folders in the game directory matching one of the values returned by <see cref="GetUninstallPaths"/>. /// /// Install flow: /// 1. Run the uninstall flow. /// 2. Copy the SMAPI files from package/Windows or package/Mono into the game directory. /// 3. On Linux/Mac: back up the game launcher and replace it with the SMAPI launcher. (This isn't possible on Windows, so the user needs to configure it manually.) /// 4. Create the 'Mods' directory. /// 5. Copy the bundled mods into the 'Mods' directory (deleting any existing versions). /// 6. Move any mods from app data into game's mods directory. /// </remarks> public void Run(string[] args) { /********* ** Step 1: initial setup *********/ /**** ** Get platform & set window title ****/ Platform platform = EnvironmentUtility.DetectPlatform(); Console.Title = $"SMAPI {this.GetDisplayVersion(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}"; Console.WriteLine(); /**** ** Check if correct installer ****/ #if SMAPI_FOR_WINDOWS if (platform == Platform.Linux || platform == Platform.Mac) { this.PrintError($"This is the installer for Windows. Run the 'install on {platform}.{(platform == Platform.Linux ? "sh" : "command")}' file instead."); Console.ReadLine(); return; } #else if (platform == Platform.Windows) { this.PrintError($"This is the installer for Linux/Mac. Run the 'install on Windows.exe' file instead."); Console.ReadLine(); return; } #endif /**** ** Check Windows dependencies ****/ if (platform == Platform.Windows) { // .NET Framework 4.5+ if (!this.HasNetFramework45(platform)) { this.PrintError(Environment.OSVersion.Version >= this.Windows7Version ? "Please install the latest version of .NET Framework before installing SMAPI." // Windows 7+ : "Please install .NET Framework 4.5 before installing SMAPI." // Windows Vista or earlier ); this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details."); Console.ReadLine(); return; } if (!this.HasXna(platform)) { this.PrintError("You don't seem to have XNA Framework installed. Please run the game at least once before installing SMAPI, so it can perform its first-time setup."); Console.ReadLine(); return; } } /**** ** read command-line arguments ****/ // get action from CLI bool installArg = args.Contains("--install"); bool uninstallArg = args.Contains("--uninstall"); if (installArg && uninstallArg) { this.PrintError("You can't specify both --install and --uninstall command-line flags."); Console.ReadLine(); return; } // get game path from CLI string gamePathArg = null; { int pathIndex = Array.LastIndexOf(args, "--game-path") + 1; if (pathIndex >= 1 && args.Length >= pathIndex) { gamePathArg = args[pathIndex]; } } /********* ** Step 2: choose a theme (can't auto-detect on Linux/Mac) *********/ MonitorColorScheme scheme = MonitorColorScheme.AutoDetect; if (platform == Platform.Linux || platform == Platform.Mac) { /**** ** print header ****/ this.PrintPlain("Hi there! I'll help you install or remove SMAPI. Just a few questions first."); this.PrintPlain("----------------------------------------------------------------------------"); Console.WriteLine(); /**** ** show theme selector ****/ // get theme writers var lightBackgroundWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.LightBackground); var darkDarkgroundWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.DarkBackground); // print question this.PrintPlain("Which text looks more readable?"); Console.WriteLine(); Console.Write(" [1] "); lightBackgroundWriter.WriteLine("Dark text on light background", ConsoleLogLevel.Info); Console.Write(" [2] "); darkDarkgroundWriter.WriteLine("Light text on dark background", ConsoleLogLevel.Info); Console.WriteLine(); // handle choice string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }); switch (choice) { case "1": scheme = MonitorColorScheme.LightBackground; this.ConsoleWriter = lightBackgroundWriter; break; case "2": scheme = MonitorColorScheme.DarkBackground; this.ConsoleWriter = darkDarkgroundWriter; break; default: throw new InvalidOperationException($"Unexpected action key '{choice}'."); } } Console.Clear(); /********* ** Step 3: find game folder *********/ InstallerPaths paths; { /**** ** print header ****/ this.PrintInfo("Hi there! I'll help you install or remove SMAPI. Just a few questions first."); this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); /**** ** collect details ****/ // get game path this.PrintInfo("Where is your game folder?"); DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, gamePathArg); if (installDir == null) { this.PrintError("Failed finding your game path."); Console.ReadLine(); return; } // get folders DirectoryInfo packageDir = new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); paths = new InstallerPaths(packageDir, installDir, EnvironmentUtility.GetExecutableName(platform)); } Console.Clear(); /********* ** Step 4: validate assumptions *********/ { 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; } } /********* ** Step 5: ask what to do *********/ ScriptAction action; { /**** ** print header ****/ this.PrintInfo("Hi there! I'll help you install or remove SMAPI. Just one question first."); this.PrintDebug($"Game path: {paths.GamePath}"); this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); /**** ** ask what to do ****/ if (installArg) { action = ScriptAction.Install; } else if (uninstallArg) { action = ScriptAction.Uninstall; } else { this.PrintInfo("What do you want to do?"); Console.WriteLine(); this.PrintInfo("[1] Install SMAPI."); this.PrintInfo("[2] Uninstall SMAPI."); Console.WriteLine(); string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }); switch (choice) { case "1": action = ScriptAction.Install; break; case "2": action = ScriptAction.Uninstall; break; default: throw new InvalidOperationException($"Unexpected action key '{choice}'."); } } } Console.Clear(); /********* ** Step 6: apply *********/ { /**** ** print header ****/ this.PrintInfo($"That's all I need! I'll {action.ToString().ToLower()} SMAPI now."); this.PrintDebug($"Game path: {paths.GamePath}"); this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); /**** ** Always uninstall old files ****/ // restore game launcher if (platform.IsMono() && File.Exists(paths.UnixBackupLauncherPath)) { this.PrintDebug("Removing SMAPI launcher..."); this.InteractivelyDelete(paths.UnixLauncherPath); File.Move(paths.UnixBackupLauncherPath, paths.UnixLauncherPath); } // remove old files string[] removePaths = this.GetUninstallPaths(paths.GameDir, paths.ModsDir) .Where(path => Directory.Exists(path) || File.Exists(path)) .ToArray(); if (removePaths.Any()) { this.PrintDebug(action == ScriptAction.Install ? "Removing previous SMAPI files..." : "Removing SMAPI files..."); foreach (string path in removePaths) { this.InteractivelyDelete(path); } } /**** ** Install new files ****/ if (action == ScriptAction.Install) { // copy SMAPI files to game dir this.PrintDebug("Adding SMAPI files..."); foreach (FileSystemInfo sourceEntry in paths.PackageDir.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); } // replace mod launcher (if possible) if (platform.IsMono()) { this.PrintDebug("Safely replacing game launcher..."); if (File.Exists(paths.UnixLauncherPath)) { if (!File.Exists(paths.UnixBackupLauncherPath)) { File.Move(paths.UnixLauncherPath, paths.UnixBackupLauncherPath); } else { this.InteractivelyDelete(paths.UnixLauncherPath); } } File.Move(paths.UnixSmapiLauncherPath, paths.UnixLauncherPath); } // create mods directory (if needed) if (!paths.ModsDir.Exists) { this.PrintDebug("Creating mods directory..."); paths.ModsDir.Create(); } // add or replace bundled mods DirectoryInfo packagedModsDir = new DirectoryInfo(Path.Combine(paths.PackageDir.FullName, "Mods")); if (packagedModsDir.Exists && packagedModsDir.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)) { // validate source mod if (sourceMod.Manifest == null) { this.PrintWarning($" ignored invalid bundled mod {sourceMod.DisplayName}: {sourceMod.ManifestParseError}"); continue; } // find target folder ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase) == true); DirectoryInfo defaultTargetFolder = new DirectoryInfo(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder; this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName ? $" adding {sourceMod.Manifest.Name}..." : $" adding {sourceMod.Manifest.Name} to {Path.Combine(paths.ModsDir.Name, PathUtilities.GetRelativePath(paths.ModsPath, targetFolder.FullName))}..." ); // (re)create target 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)); } } } // set SMAPI's color scheme if defined if (scheme != MonitorColorScheme.AutoDetect) { string text = File .ReadAllText(paths.ApiConfigPath) .Replace(@"""ColorScheme"": ""AutoDetect""", $@"""ColorScheme"": ""{scheme}"""); File.WriteAllText(paths.ApiConfigPath, text); } // remove obsolete appdata mods this.InteractivelyRemoveAppDataMods(paths.ModsDir, packagedModsDir); } } Console.WriteLine(); Console.WriteLine(); /********* ** Step 7: final instructions *********/ if (platform == Platform.Windows) { if (action == ScriptAction.Install) { this.PrintSuccess("SMAPI is installed! If you use Steam, set your launch options to enable achievements (see smapi.io/install):"); this.PrintSuccess($" \"{Path.Combine(paths.GamePath, "StardewModdingAPI.exe")}\" %command%"); Console.WriteLine(); this.PrintSuccess("If you don't use Steam, launch StardewModdingAPI.exe in your game folder to play with mods."); } else { this.PrintSuccess("SMAPI is removed! If you configured Steam to launch SMAPI, don't forget to clear your launch options."); } } else { this.PrintSuccess(action == ScriptAction.Install ? "SMAPI is installed! Launch the game the same way as before to play with mods." : "SMAPI is removed! Launch the game the same way as before to play without mods." ); } Console.ReadKey(); }
/// <summary>Run the install or uninstall script.</summary> /// <param name="args">The command line arguments.</param> /// <remarks> /// Initialization flow: /// 1. Collect information (mainly OS and install path) and validate it. /// 2. Ask the user whether to install or uninstall. /// /// Uninstall logic: /// 1. On Linux/Mac: if a backup of the launcher exists, delete the launcher and restore the backup. /// 2. Delete all files and folders in the game directory matching one of the values returned by <see cref="GetUninstallPaths"/>. /// /// Install flow: /// 1. Run the uninstall flow. /// 2. Copy the SMAPI files from package/Windows or package/Mono into the game directory. /// 3. On Linux/Mac: back up the game launcher and replace it with the SMAPI launcher. (This isn't possible on Windows, so the user needs to configure it manually.) /// 4. Create the 'Mods' directory. /// 5. Copy the bundled mods into the 'Mods' directory (deleting any existing versions). /// 6. Move any mods from app data into game's mods directory. /// </remarks> public void Run(string[] args) { /********* ** Step 1: initial setup *********/ /**** ** Get basic info & set window title ****/ ModToolkit toolkit = new ModToolkit(); var context = new InstallerContext(); Console.Title = $"SMAPI {context.GetInstallerVersion()} installer on {context.Platform} {context.PlatformName}"; Console.WriteLine(); /**** ** Check if correct installer ****/ #if SMAPI_FOR_WINDOWS if (context.IsUnix) { this.PrintError($"This is the installer for Windows. Run the 'install on {context.Platform}.{(context.Platform == Platform.Mac ? "command" : "sh")}' file instead."); Console.ReadLine(); return; } #else if (context.IsWindows) { this.PrintError($"This is the installer for Linux/Mac. Run the 'install on Windows.exe' file instead."); Console.ReadLine(); return; } #endif /**** ** Check Windows dependencies ****/ if (context.IsWindows) { // .NET Framework 4.5+ if (!context.HasNetFramework45()) { this.PrintError(context.CanInstallLatestNetFramework() ? "Please install the latest version of .NET Framework before installing SMAPI." : "Please install .NET Framework 4.5 before installing SMAPI." ); this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details."); Console.ReadLine(); return; } if (!context.HasXna()) { this.PrintError("You don't seem to have XNA Framework installed. Please run the game at least once before installing SMAPI, so it can perform its first-time setup."); Console.ReadLine(); return; } } /**** ** read command-line arguments ****/ // get action from CLI bool installArg = args.Contains("--install"); bool uninstallArg = args.Contains("--uninstall"); if (installArg && uninstallArg) { this.PrintError("You can't specify both --install and --uninstall command-line flags."); Console.ReadLine(); return; } // get game path from CLI string gamePathArg = null; { int pathIndex = Array.LastIndexOf(args, "--game-path") + 1; if (pathIndex >= 1 && args.Length >= pathIndex) { gamePathArg = args[pathIndex]; } } /********* ** Step 2: choose a theme (can't auto-detect on Linux/Mac) *********/ MonitorColorScheme scheme = MonitorColorScheme.AutoDetect; if (context.IsUnix) { /**** ** print header ****/ this.PrintPlain("Hi there! I'll help you install or remove SMAPI. Just a few questions first."); this.PrintPlain("----------------------------------------------------------------------------"); Console.WriteLine(); /**** ** show theme selector ****/ // get theme writers var lightBackgroundWriter = new ColorfulConsoleWriter(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground)); var darkBackgroundWriter = new ColorfulConsoleWriter(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground)); // print question this.PrintPlain("Which text looks more readable?"); Console.WriteLine(); Console.Write(" [1] "); lightBackgroundWriter.WriteLine("Dark text on light background", ConsoleLogLevel.Info); Console.Write(" [2] "); darkBackgroundWriter.WriteLine("Light text on dark background", ConsoleLogLevel.Info); Console.WriteLine(); // handle choice string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }); switch (choice) { case "1": scheme = MonitorColorScheme.LightBackground; this.ConsoleWriter = lightBackgroundWriter; break; case "2": scheme = MonitorColorScheme.DarkBackground; this.ConsoleWriter = darkBackgroundWriter; break; default: throw new InvalidOperationException($"Unexpected action key '{choice}'."); } } Console.Clear(); /********* ** Step 3: find game folder *********/ InstallerPaths paths; { /**** ** print header ****/ this.PrintInfo("Hi there! I'll help you install or remove SMAPI. Just a few questions first."); this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); /**** ** collect details ****/ // get game path this.PrintInfo("Where is your game folder?"); DirectoryInfo installDir = this.InteractivelyGetInstallPath(toolkit, context, gamePathArg); if (installDir == null) { this.PrintError("Failed finding your game path."); Console.ReadLine(); return; } // get folders DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath); paths = new InstallerPaths(bundleDir, installDir, context.ExecutableName); } Console.Clear(); /********* ** Step 4: validate assumptions *********/ if (!File.Exists(paths.ExecutablePath)) { this.PrintError("The detected game install path doesn't contain a Stardew Valley executable."); Console.ReadLine(); return; } /********* ** Step 5: ask what to do *********/ ScriptAction action; { /**** ** print header ****/ this.PrintInfo("Hi there! I'll help you install or remove SMAPI. Just one question first."); this.PrintDebug($"Game path: {paths.GamePath}"); this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); /**** ** ask what to do ****/ if (installArg) { action = ScriptAction.Install; } else if (uninstallArg) { action = ScriptAction.Uninstall; } else { this.PrintInfo("What do you want to do?"); Console.WriteLine(); this.PrintInfo("[1] Install SMAPI."); this.PrintInfo("[2] Uninstall SMAPI."); Console.WriteLine(); string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }); switch (choice) { case "1": action = ScriptAction.Install; break; case "2": action = ScriptAction.Uninstall; break; default: throw new InvalidOperationException($"Unexpected action key '{choice}'."); } } } Console.Clear(); /********* ** Step 6: apply *********/ { /**** ** print header ****/ this.PrintInfo($"That's all I need! I'll {action.ToString().ToLower()} SMAPI now."); this.PrintDebug($"Game path: {paths.GamePath}"); this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); /**** ** Back up user settings ****/ if (File.Exists(paths.ApiUserConfigPath)) { File.Copy(paths.ApiUserConfigPath, paths.BundleApiUserConfigPath); } /**** ** Always uninstall old files ****/ // restore game launcher if (context.IsUnix && File.Exists(paths.UnixBackupLauncherPath)) { this.PrintDebug("Removing SMAPI launcher..."); this.InteractivelyDelete(paths.UnixLauncherPath); File.Move(paths.UnixBackupLauncherPath, paths.UnixLauncherPath); } // remove old files string[] removePaths = this.GetUninstallPaths(paths.GameDir, paths.ModsDir) .Where(path => Directory.Exists(path) || File.Exists(path)) .ToArray(); if (removePaths.Any()) { this.PrintDebug(action == ScriptAction.Install ? "Removing previous SMAPI files..." : "Removing SMAPI files..."); foreach (string path in removePaths) { this.InteractivelyDelete(path); } } // move global save data folder (changed in 3.2) { string dataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); DirectoryInfo oldDir = new DirectoryInfo(Path.Combine(dataPath, "Saves", ".smapi")); DirectoryInfo newDir = new DirectoryInfo(Path.Combine(dataPath, ".smapi")); if (oldDir.Exists) { if (newDir.Exists) { this.InteractivelyDelete(oldDir.FullName); } else { oldDir.MoveTo(newDir.FullName); } } } /**** ** Install new files ****/ if (action == ScriptAction.Install) { // copy SMAPI files to game dir this.PrintDebug("Adding SMAPI files..."); foreach (FileSystemInfo sourceEntry in paths.BundleDir.EnumerateFileSystemInfos().Where(this.ShouldCopy)) { this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name)); this.RecursiveCopy(sourceEntry, paths.GameDir); } // replace mod launcher (if possible) if (context.IsUnix) { this.PrintDebug("Safely replacing game launcher..."); // back up & remove current launcher if (File.Exists(paths.UnixLauncherPath)) { if (!File.Exists(paths.UnixBackupLauncherPath)) { File.Move(paths.UnixLauncherPath, paths.UnixBackupLauncherPath); } else { 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.) // (Note: exclude from Windows build because antivirus apps can flag the process start code as suspicious.) #if !SMAPI_FOR_WINDOWS new Process { StartInfo = new ProcessStartInfo { FileName = "chmod", Arguments = $"755 \"{paths.UnixLauncherPath}\"", CreateNoWindow = true } }.Start(); #endif } // create mods directory (if needed) if (!paths.ModsDir.Exists) { this.PrintDebug("Creating mods directory..."); paths.ModsDir.Create(); } // add or replace bundled mods DirectoryInfo bundledModsDir = new DirectoryInfo(Path.Combine(paths.BundlePath, "Mods")); if (bundledModsDir.Exists && bundledModsDir.EnumerateDirectories().Any()) { this.PrintDebug("Adding bundled mods..."); ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath).ToArray(); foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName)) { // validate source mod if (sourceMod.Manifest == null) { 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.OrdinalIgnoreCase) == true); DirectoryInfo defaultTargetFolder = new DirectoryInfo(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder; this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName ? $" adding {sourceMod.Manifest.Name}..." : $" adding {sourceMod.Manifest.Name} to {Path.Combine(paths.ModsDir.Name, PathUtilities.GetRelativePath(paths.ModsPath, targetFolder.FullName))}..." ); // remove existing folder if (targetFolder.Exists) { this.InteractivelyDelete(targetFolder.FullName); } // copy files this.RecursiveCopy(sourceMod.Directory, paths.ModsDir, filter: this.ShouldCopy); } } // set SMAPI's color scheme if defined if (scheme != MonitorColorScheme.AutoDetect) { string text = File .ReadAllText(paths.ApiConfigPath) .Replace(@"""UseScheme"": ""AutoDetect""", $@"""UseScheme"": ""{scheme}"""); File.WriteAllText(paths.ApiConfigPath, text); } // remove obsolete appdata mods this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir); } } Console.WriteLine(); Console.WriteLine(); /********* ** Step 7: final instructions *********/ if (context.IsWindows) { if (action == ScriptAction.Install) { this.PrintSuccess("SMAPI is installed! If you use Steam, set your launch options to enable achievements (see smapi.io/install):"); this.PrintSuccess($" \"{Path.Combine(paths.GamePath, "StardewModdingAPI.exe")}\" %command%"); Console.WriteLine(); this.PrintSuccess("If you don't use Steam, launch StardewModdingAPI.exe in your game folder to play with mods."); } else { this.PrintSuccess("SMAPI is removed! If you configured Steam to launch SMAPI, don't forget to clear your launch options."); } } else { this.PrintSuccess(action == ScriptAction.Install ? "SMAPI is installed! Launch the game the same way as before to play with mods." : "SMAPI is removed! Launch the game the same way as before to play without mods." ); } Console.ReadKey(); }