/********* ** Public methods *********/ /// <summary>Run the install or uninstall script.</summary> /// <param name="args">The command line arguments.</param> public static void Main(string[] args) { // set up assembly resolution string installerPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); Program.DllSearchPath = EnvironmentUtility.DetectPlatform() == Platform.Windows ? Path.Combine(installerPath, "internal", "Windows", "smapi-internal") : Path.Combine(installerPath, "smapi-internal"); AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve; // launch installer var installer = new InteractiveInstaller(); installer.Run(args); }
/// <summary>Assert that the game is available.</summary> /// <remarks>This must be checked *before* any references to <see cref="Constants"/>, and this method should not reference <see cref="Constants"/> itself to avoid errors in Mono.</remarks> private static void AssertGamePresent() { Platform platform = EnvironmentUtility.DetectPlatform(); string gameAssemblyName = platform == Platform.Windows ? "Stardew Valley" : "StardewValley"; if (Type.GetType($"StardewValley.Game1, {gameAssemblyName}", throwOnError: false) == null) { Program.PrintErrorAndExit( "Oops! SMAPI can't find the game. " + (Assembly.GetCallingAssembly().Location.Contains(Path.Combine("internal", "Windows")) || Assembly.GetCallingAssembly().Location.Contains(Path.Combine("internal", "Mono")) ? "It looks like you're running SMAPI from the download package, but you need to run the installed version instead. " : "Make sure you're running StardewModdingAPI.exe in your game folder. " ) + "See the readme.txt file for details." ); } }
/********* ** Private methods *********/ /// <summary>Get the font glyph data for a MonoGame font.</summary> /// <param name="font">The sprite font.</param> private IDictionary <char, object> GetGlyphs(SpriteFont font) { Platform platform = EnvironmentUtility.DetectPlatform(); IDictionary <char, object> glyphs = new Dictionary <char, object>(); if (platform == Platform.Windows) { // get internal sprite data IList <Rectangle> glyphData = this.RequireField <List <Rectangle> >(font, "glyphData"); IList <Rectangle> croppingData = this.RequireField <List <Rectangle> >(font, "croppingData"); IList <Vector3> kerning = this.RequireField <List <Vector3> >(font, "kerning"); // replicate MonoGame structure for consistency (and readability) for (int i = 0; i < font.Characters.Count; i++) { char ch = font.Characters[i]; glyphs[ch] = new { BoundsInTexture = glyphData[i], Cropping = croppingData[i], Character = ch, LeftSideBearing = kerning[i].X, Width = kerning[i].Y, RightSideBearing = kerning[i].Z, WidthIncludingBearings = kerning[i].X + kerning[i].Y + kerning[i].Z }; } } else { //use Mono exclusive method foreach (DictionaryEntry entry in this.RequireMethod <IDictionary>(font, "GetGlyphs")) { glyphs.Add((char)entry.Key, entry.Value); } } return(glyphs); }
/********* ** Public methods *********/ /// <summary>Find all valid Stardew Valley install folders.</summary> /// <remarks>This checks default game locations, and on Windows checks the Windows registry for GOG/Steam install data. A folder is considered 'valid' if it contains the Stardew Valley executable for the current OS.</remarks> public IEnumerable <DirectoryInfo> Scan() { // get OS info Platform platform = EnvironmentUtility.DetectPlatform(); string executableFilename = EnvironmentUtility.GetExecutableName(platform); // get install paths IEnumerable <string> paths = this .GetCustomInstallPaths(platform) .Concat(this.GetDefaultInstallPaths(platform)) .Select(PathUtilities.NormalizePathSeparators) .Distinct(StringComparer.OrdinalIgnoreCase); // yield valid folders foreach (string path in paths) { DirectoryInfo folder = new DirectoryInfo(path); if (folder.Exists && folder.EnumerateFiles(executableFilename).Any()) { yield return(folder); } } }
/// <summary>Get the game's executable assembly name.</summary> private static string GetExecutableAssemblyName() { Platform platform = EnvironmentUtility.DetectPlatform(); return(platform == Platform.Windows ? "Stardew Valley" : "StardewValley"); }
/// <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(); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> public InteractiveInstaller() { this.ConsoleWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.AutoDetect); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> public GameScanner() { this.Platform = EnvironmentUtility.DetectPlatform(); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <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()); }
/// <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) { /**** ** Get platform & set window title ****/ Platform platform = EnvironmentUtility.DetectPlatform(); Console.Title = $"SMAPI {new SemanticVersionImpl(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}"; Console.WriteLine(); #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; } #endif /**** ** 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]; } } /**** ** collect details ****/ // get game path DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, gamePathArg); if (installDir == null) { this.PrintError("Failed finding your game path."); Console.ReadLine(); return; } // get folders DirectoryInfo packageDir = platform.IsMono() ? new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)) // installer runs from internal folder on Mono : new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "internal", "Windows")); DirectoryInfo modsDir = new DirectoryInfo(Path.Combine(installDir.FullName, "Mods")); var paths = new { executable = Path.Combine(installDir.FullName, EnvironmentUtility.GetExecutableName(platform)), unixSmapiLauncher = Path.Combine(installDir.FullName, "StardewModdingAPI"), unixLauncher = Path.Combine(installDir.FullName, "StardewValley"), unixLauncherBackup = Path.Combine(installDir.FullName, "StardewValley-original") }; // show output this.PrintInfo($"Your game folder: {installDir}."); /**** ** validate assumptions ****/ if (!packageDir.Exists) { this.PrintError(platform == Platform.Windows && packageDir.FullName.Contains(Path.GetTempPath()) && packageDir.FullName.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/{packageDir.Name}' package folder is missing (should be at {packageDir})." ); Console.ReadLine(); return; } if (!File.Exists(paths.executable)) { this.PrintError("The detected game install path doesn't contain a Stardew Valley executable."); Console.ReadLine(); return; } /**** ** validate 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; } } Console.WriteLine(); /**** ** ask user what to do ****/ ScriptAction action; if (installArg) { action = ScriptAction.Install; } else if (uninstallArg) { action = ScriptAction.Uninstall; } else { this.PrintInfo("You can...."); this.PrintInfo("[1] Install SMAPI."); this.PrintInfo("[2] Uninstall SMAPI."); Console.WriteLine(); string choice = this.InteractivelyChoose("What do you want to do? Type 1 or 2, then press enter.", "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.WriteLine(); } /**** ** Always uninstall old files ****/ // restore game launcher if (platform.IsMono() && File.Exists(paths.unixLauncherBackup)) { this.PrintDebug("Removing SMAPI launcher..."); this.InteractivelyDelete(paths.unixLauncher); File.Move(paths.unixLauncherBackup, paths.unixLauncher); } // remove old files string[] removePaths = this.GetUninstallPaths(installDir, 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 (FileInfo sourceFile in packageDir.EnumerateFiles().Where(this.ShouldCopyFile)) { if (sourceFile.Name == this.InstallerFileName) { continue; } string targetPath = Path.Combine(installDir.FullName, sourceFile.Name); this.InteractivelyDelete(targetPath); sourceFile.CopyTo(targetPath); } // replace mod launcher (if possible) if (platform.IsMono()) { this.PrintDebug("Safely replacing game launcher..."); if (File.Exists(paths.unixLauncher)) { if (!File.Exists(paths.unixLauncherBackup)) { File.Move(paths.unixLauncher, paths.unixLauncherBackup); } else { this.InteractivelyDelete(paths.unixLauncher); } } File.Move(paths.unixSmapiLauncher, paths.unixLauncher); } // create mods directory (if needed) if (!modsDir.Exists) { this.PrintDebug("Creating mods directory..."); modsDir.Create(); } // add or replace bundled mods modsDir.Create(); DirectoryInfo packagedModsDir = new DirectoryInfo(Path.Combine(packageDir.FullName, "Mods")); if (packagedModsDir.Exists && packagedModsDir.EnumerateDirectories().Any()) { this.PrintDebug("Adding bundled mods..."); // special case: rename Omegasis' SaveBackup mod { DirectoryInfo oldFolder = new DirectoryInfo(Path.Combine(modsDir.FullName, "SaveBackup")); DirectoryInfo newFolder = new DirectoryInfo(Path.Combine(modsDir.FullName, "AdvancedSaveBackup")); FileInfo manifest = new FileInfo(Path.Combine(oldFolder.FullName, "manifest.json")); if (manifest.Exists && !newFolder.Exists && File.ReadLines(manifest.FullName).Any(p => p.IndexOf("Omegasis", StringComparison.InvariantCultureIgnoreCase) != -1)) { this.PrintDebug($" moving {oldFolder.Name} to {newFolder.Name}..."); this.Move(oldFolder, newFolder.FullName); } } // add bundled mods foreach (DirectoryInfo sourceDir in packagedModsDir.EnumerateDirectories()) { this.PrintDebug($" adding {sourceDir.Name}..."); // initialise target dir DirectoryInfo targetDir = new DirectoryInfo(Path.Combine(modsDir.FullName, sourceDir.Name)); this.InteractivelyDelete(targetDir.FullName); targetDir.Create(); // copy files foreach (FileInfo sourceFile in sourceDir.EnumerateFiles().Where(this.ShouldCopyFile)) { sourceFile.CopyTo(Path.Combine(targetDir.FullName, sourceFile.Name)); } } } // remove obsolete appdata mods this.InteractivelyRemoveAppDataMods(modsDir, packagedModsDir); } Console.WriteLine(); Console.WriteLine(); /**** ** 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(installDir.FullName, "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(); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> public InstallerContext() { this.Platform = EnvironmentUtility.DetectPlatform(); this.PlatformName = EnvironmentUtility.GetFriendlyPlatformName(this.Platform); }
public void Unpack(string call, string[] parameter) { IAssetWriter[] assetWriters = { new MapWriter(), new SpriteFontWriter(), new TextureWriter(), new XmlSourceWriter(), new DataWriter() // check last due to more expensive CanWrite }; Platform platform = EnvironmentUtility.DetectPlatform(); string gamePath = Constants.ExecutionPath; this.Monitor.Log($"Found game folder: {gamePath}.", LogLevel.Info); this.Monitor.Log(""); // get import/export paths string contentPath = Path.Combine(gamePath, "Content"); string exportPath = Path.Combine(gamePath, "Content (unpacked)"); // symlink files on Linux/Mac if (platform == Platform.Linux || platform == Platform.Mac) { Process.Start("ln", $"-sf \"{Path.Combine(gamePath, "Content")}\""); Process.Start("ln", $"-sf \"{Path.Combine(gamePath, "lib")}\""); Process.Start("ln", $"-sf \"{Path.Combine(gamePath, "lib64")}\""); } ConsoleProgressBar progressBar; Console.ForegroundColor = ConsoleColor.DarkGray; Game1 game = Game1.game1; this.Monitor.Log(""); this.Monitor.Log("Unpacking files...", LogLevel.Info); // collect files DirectoryInfo contentDir = new DirectoryInfo(contentPath); FileInfo[] files = contentDir.EnumerateFiles("*.xnb", SearchOption.AllDirectories).ToArray(); progressBar = new ModConsoleProgressBar(this.Monitor, files.Length, Console.Title); // write assets foreach (FileInfo file in files) { // prepare paths string assetName = file.FullName.Substring(contentPath.Length + 1, file.FullName.Length - contentPath.Length - 5); // remove root path + .xnb extension string fileExportPath = Path.Combine(exportPath, assetName); Directory.CreateDirectory(Path.GetDirectoryName(fileExportPath)); // show progress bar progressBar.Increment(); progressBar.Print(assetName); // read asset object asset = null; try { asset = game.Content.Load <object>(assetName); } catch (Exception ex) { this.Monitor.Log($"{assetName} => read error: {ex.Message}", LogLevel.Error); continue; } // write asset try { // get writer IAssetWriter writer = assetWriters.FirstOrDefault(p => p.CanWrite(asset)); // write file if (writer == null) { this.Monitor.Log($"{assetName}.xnb ({asset.GetType().Name}) isn't a supported asset type.", LogLevel.Warn); File.Copy(file.FullName, $"{fileExportPath}.xnb", overwrite: true); } else if (!writer.TryWriteFile(asset, fileExportPath, assetName, platform, out string writeError)) { this.Monitor.Log($"{assetName}.xnb ({asset.GetType().Name}) could not be saved: {writeError}.", LogLevel.Warn); File.Copy(file.FullName, $"{fileExportPath}.xnb", overwrite: true); } } catch (Exception ex) { this.Monitor.Log($"{assetName} => export error: {ex.Message}", LogLevel.Error); } finally { game.Content.Unload(); } } this.Monitor.Log($"Done! Unpacked files to {exportPath}.", LogLevel.Info); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> public GameScanner() { this.Platform = EnvironmentUtility.DetectPlatform(); this.ExecutableName = EnvironmentUtility.GetExecutableName(this.Platform); }
/// <summary>Unpack all assets in the content folder and store them in the output folder.</summary> public void Run() { // get game info Platform platform = EnvironmentUtility.DetectPlatform(); string gamePath = new ModToolkit().GetGameFolders().FirstOrDefault()?.FullName; if (gamePath == null) { this.PrintColor("Can't find Stardew Valley folder.", ConsoleColor.Red); return; } Console.WriteLine($"Found game folder: {gamePath}."); Console.WriteLine(); // get import/export paths string contentPath = Path.Combine(gamePath, "Content"); string exportPath = Path.Combine(gamePath, "Content (unpacked)"); // symlink files on Linux/Mac if (platform == Platform.Linux || platform == Platform.Mac) { Process.Start("ln", $"-sf \"{Path.Combine(gamePath, "Content")}\""); Process.Start("ln", $"-sf \"{Path.Combine(gamePath, "lib")}\""); Process.Start("ln", $"-sf \"{Path.Combine(gamePath, "lib64")}\""); } // load game ConsoleProgressBar progressBar; Console.WriteLine("Loading game instance..."); Console.ForegroundColor = ConsoleColor.DarkGray; using (Game1 game = this.GetGameInstance(platform, contentPath)) { Console.ResetColor(); Console.WriteLine(); Console.WriteLine("Unpacking files..."); // collect files DirectoryInfo contentDir = new DirectoryInfo(contentPath); FileInfo[] files = contentDir.EnumerateFiles("*.xnb", SearchOption.AllDirectories).ToArray(); progressBar = new ConsoleProgressBar(files.Length); // write assets foreach (FileInfo file in files) { // prepare paths string assetName = file.FullName.Substring(contentPath.Length + 1, file.FullName.Length - contentPath.Length - 5); // remove root path + .xnb extension string fileExportPath = Path.Combine(exportPath, assetName); Directory.CreateDirectory(Path.GetDirectoryName(fileExportPath)); // show progress bar progressBar.Increment(); progressBar.Print(assetName); // read asset object asset = null; try { asset = game.Content.Load <object>(assetName); } catch (Exception ex) { progressBar.Erase(); this.PrintColor($"{assetName} => read error: {ex.Message}", ConsoleColor.Red); continue; } // write asset try { // get writer IAssetWriter writer = this.AssetWriters.FirstOrDefault(p => p.CanWrite(asset)); // write file if (writer == null) { progressBar.Erase(); this.PrintColor($"{assetName}.xnb ({asset.GetType().Name}) isn't a supported asset type.", ConsoleColor.DarkYellow); File.Copy(file.FullName, $"{fileExportPath}.xnb", overwrite: true); } else if (!writer.TryWriteFile(asset, fileExportPath, assetName, platform, out string writeError)) { progressBar.Erase(); this.PrintColor($"{assetName}.xnb ({asset.GetType().Name}) could not be saved: {writeError}.", ConsoleColor.DarkYellow); File.Copy(file.FullName, $"{fileExportPath}.xnb", overwrite: true); } } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"{assetName} => export error: {ex.Message}"); Console.ResetColor(); } finally { game.Content.Unload(); } } } progressBar.Erase(); Console.WriteLine($"Done! Unpacked files to {exportPath}."); }