Example #1
0
        /*********
        ** Public methods
        *********/
        /// <summary>The console app entry method.</summary>
        internal static void Main()
        {
            // Add fallback assembly resolution that loads DLLs from a 'smapi-internal' subfolder,
            // so it can be run from the game folder. This must be set before any references to
            // game or toolkit types (including IAssetWriter which references the toolkit's
            // Platform enum).
            AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve;

            // launch app
            try
            {
                Program.Run();
            }
            catch (Exception ex)
            {
                // not in game folder
                if (ex is FileNotFoundException fileNotFoundEx)
                {
                    AssemblyName assemblyName = new AssemblyName(fileNotFoundEx.FileName);
                    if (assemblyName.Name == "Stardew Valley" || assemblyName.Name == "StardewValley")
                    {
                        Console.WriteLine("Oops! StardewXnbHack must be placed in the Stardew Valley game folder.\nSee instructions: https://github.com/Pathoschild/StardewXnbHack#readme.");
                        DefaultConsoleLogger.PressAnyKeyToExit();
                        return;
                    }
                }

                // generic unhandled exception
                Console.WriteLine("Oops! Something went wrong running the unpacker:");
                Console.WriteLine(ex.ToString());
                DefaultConsoleLogger.PressAnyKeyToExit();
            }
        }
Example #2
0
        /// <summary>Unpack all assets in the content folder and store them in the output folder.</summary>
        /// <param name="game">The game instance through which to unpack files, or <c>null</c> to launch a temporary internal instance.</param>
        /// <param name="gamePath">The absolute path to the game folder, or <c>null</c> to auto-detect it.</param>
        /// <param name="getLogger">Get a custom progress update logger, or <c>null</c> to use the default console logging. Receives the unpack context and default logger as arguments.</param>
        /// <param name="showPressAnyKeyToExit">Whether the default logger should show a 'press any key to exit' prompt when it finishes.</param>
        public static void Run(GameRunner game = null, string gamePath = null, Func <IUnpackContext, IProgressLogger, IProgressLogger> getLogger = null, bool showPressAnyKeyToExit = true)
        {
            // init logging
            UnpackContext   context = new UnpackContext();
            IProgressLogger logger  = new DefaultConsoleLogger(context, showPressAnyKeyToExit);

            try
            {
                // get override logger
                if (getLogger != null)
                {
                    logger = getLogger(context, logger);
                }

                // start timer
                Stopwatch timer = new Stopwatch();
                timer.Start();

                // get asset writers
                var assetWriters = new IAssetWriter[]
                {
                    new MapWriter(),
                    new SpriteFontWriter(),
                    new TextureWriter(),
                    new XmlSourceWriter(),
                    new DataWriter() // check last due to more expensive CanWrite
                };

                // get paths
                var platform = new PlatformContext();
                {
                    if (platform.TryDetectGamePaths(gamePath, out gamePath, out string contentPath))
                    {
                        context.GamePath    = gamePath;
                        context.ContentPath = contentPath;
                    }
                    else
                    {
                        logger.OnFatalError(gamePath == null
                            ? "Can't find Stardew Valley folder. Try running StardewXnbHack from the game folder instead."
                            : $"Can't find the content folder for the game at {gamePath}. Is the game installed correctly?"
                                            );
                        return;
                    }
                }
                context.ExportPath = Path.Combine(context.GamePath, "Content (unpacked)");
                logger.OnStepChanged(ProgressStep.GameFound, $"Found game folder: {context.GamePath}.");

                // symlink files on Linux/Mac
                if (platform.Is(Platform.Linux, Platform.Mac))
                {
                    foreach (string dirName in new[] { "lib", "lib64" })
                    {
                        string fullPath = Path.Combine(context.GamePath, dirName);
                        if (!Directory.Exists(dirName))
                        {
                            Process.Start("ln", $"-sf \"{fullPath}\"");
                        }
                    }
                }

                // load game instance
                bool disposeGame = false;
                if (game == null)
                {
                    logger.OnStepChanged(ProgressStep.LoadingGameInstance, "Loading game instance...");
                    game        = Program.CreateTemporaryGameInstance(platform, context.ContentPath);
                    disposeGame = true;
                }

                // unpack files
                try
                {
                    logger.OnStepChanged(ProgressStep.UnpackingFiles, "Unpacking files...");

                    // collect files
                    DirectoryInfo contentDir = new DirectoryInfo(context.ContentPath);
                    FileInfo[]    files      = contentDir.EnumerateFiles("*.xnb", SearchOption.AllDirectories).ToArray();
                    context.Files = files;

                    // write assets
                    foreach (FileInfo file in files)
                    {
                        // prepare paths
                        string assetName      = file.FullName.Substring(context.ContentPath.Length + 1, file.FullName.Length - context.ContentPath.Length - 5); // remove root path + .xnb extension
                        string relativePath   = $"{assetName}.xnb";
                        string fileExportPath = Path.Combine(context.ExportPath, assetName);
                        Directory.CreateDirectory(Path.GetDirectoryName(fileExportPath));

                        // fallback logic
                        void ExportRawXnb()
                        {
                            File.Copy(file.FullName, $"{fileExportPath}.xnb", overwrite: true);
                        }

                        // show progress bar
                        logger.OnFileUnpacking(assetName);

                        // read asset
                        object asset = null;
                        try
                        {
                            asset = game.Content.Load <object>(assetName);
                        }
                        catch (Exception ex)
                        {
                            if (platform.Platform.IsMono() && ex.Message == "This does not appear to be a MonoGame MGFX file!")
                            {
                                logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnsupportedFileType, $"{nameof(Effect)} isn't a supported asset type."); // use same friendly error as Windows
                            }
                            else
                            {
                                logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.ReadError, $"read error: {ex.Message}");
                            }
                            ExportRawXnb();
                            continue;
                        }

                        // write asset
                        try
                        {
                            // get writer
                            IAssetWriter writer = assetWriters.FirstOrDefault(p => p.CanWrite(asset));

                            // write file
                            if (writer == null)
                            {
                                logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnsupportedFileType, $"{asset.GetType().Name} isn't a supported asset type.");
                                ExportRawXnb();
                            }
                            else if (!writer.TryWriteFile(asset, fileExportPath, assetName, platform.Platform, out string writeError))
                            {
                                logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.WriteError, $"{asset.GetType().Name} file could not be saved: {writeError}.");
                                ExportRawXnb();
                            }
                        }
                        catch (Exception ex)
                        {
                            logger.OnFileUnpackFailed(relativePath, UnpackFailedReason.UnknownError, $"unhandled export error: {ex.Message}");
                        }
                        finally
                        {
                            game.Content.Unload();
                        }
                    }
                }
                finally
                {
                    if (disposeGame)
                    {
                        game.Dispose();
                    }
                }

                logger.OnStepChanged(ProgressStep.Done, $"Done! Unpacked {context.Files.Count()} files in {Program.GetHumanTime(timer.Elapsed)}.\nUnpacked into {context.ExportPath}.");
            }
            catch (Exception ex)
            {
                logger.OnFatalError($"Unhandled exception: {ex}");
            }
            finally
            {
                logger.OnEnded();
            }
        }