Ejemplo n.º 1
0
        /*********
        ** 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);
        }
Ejemplo n.º 2
0
        /// <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."
                    );
            }
        }
Ejemplo n.º 3
0
        /*********
        ** 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);
        }
Ejemplo n.º 4
0
        /*********
        ** 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);
                }
            }
        }
Ejemplo n.º 5
0
        /// <summary>Get the game's executable assembly name.</summary>
        private static string GetExecutableAssemblyName()
        {
            Platform platform = EnvironmentUtility.DetectPlatform();

            return(platform == Platform.Windows ? "Stardew Valley" : "StardewValley");
        }
Ejemplo n.º 6
0
        /// <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();
        }
Ejemplo n.º 7
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 public InteractiveInstaller()
 {
     this.ConsoleWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.AutoDetect);
 }
Ejemplo n.º 8
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 public GameScanner()
 {
     this.Platform = EnvironmentUtility.DetectPlatform();
 }
Ejemplo n.º 9
0
 /*********
 ** 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());
 }
Ejemplo n.º 10
0
        /// <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();
        }
Ejemplo n.º 11
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 public InstallerContext()
 {
     this.Platform     = EnvironmentUtility.DetectPlatform();
     this.PlatformName = EnvironmentUtility.GetFriendlyPlatformName(this.Platform);
 }
Ejemplo n.º 12
0
        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);
        }
Ejemplo n.º 13
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 public GameScanner()
 {
     this.Platform       = EnvironmentUtility.DetectPlatform();
     this.ExecutableName = EnvironmentUtility.GetExecutableName(this.Platform);
 }
Ejemplo n.º 14
0
        /// <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}.");
        }