/// <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(); } }
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); }
/// <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}."); }