From 92615d96924506c350853fc975a6433fb5582f4f Mon Sep 17 00:00:00 2001 From: Miepee <38186597+Miepee@users.noreply.github.com> Date: Thu, 21 Apr 2022 13:15:53 +0200 Subject: [PATCH 1/3] CLI: initial dumping support --- UndertaleModCli/CommandOptions.cs | 36 +++++++ UndertaleModCli/Program.cs | 163 ++++++++++++++++++++++++++++-- 2 files changed, 189 insertions(+), 10 deletions(-) diff --git a/UndertaleModCli/CommandOptions.cs b/UndertaleModCli/CommandOptions.cs index 5dd0610af..357c8fab5 100644 --- a/UndertaleModCli/CommandOptions.cs +++ b/UndertaleModCli/CommandOptions.cs @@ -79,4 +79,40 @@ public class InfoOptions /// public bool Verbose { get; set; } = false; } + + /// + /// Cli options for the Info command + /// + public class DumpOptions + { + /// + /// File path to the data file + /// + public FileInfo Datafile { get; set; } + + /// + /// Directory path to where to dump all contents + /// + public DirectoryInfo? Output { get; set; } + + /// + /// Determines if Cli should print out verbose logs + /// + public bool Verbose { get; set; } = false; + + /// + /// Names of the code entries that should get dumped + /// + public string[] Code { get; set; } + + /// + /// Determines if strings should get dumped. + /// + public bool Strings { get; set; } + + /// + /// Determines if embedded textures should get dumped + /// + public bool Textures { get; set; } + } } \ No newline at end of file diff --git a/UndertaleModCli/Program.cs b/UndertaleModCli/Program.cs index 33e66cbd8..523a13232 100644 --- a/UndertaleModCli/Program.cs +++ b/UndertaleModCli/Program.cs @@ -12,8 +12,11 @@ using System.CommandLine.Invocation; using System.CommandLine.Builder; using System.CommandLine.Parsing; +using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; +using UndertaleModLib.Models; namespace UndertaleModCli { @@ -42,9 +45,9 @@ public partial class Program : IScriptInterface private bool Verbose { get; } /// - /// File path to where to save the modified data file + /// File path or directory path that determines an output for the current Program. /// - private FileInfo? Output { get; } + private FileSystemInfo? Output { get; } //TODO: document these, these are intertwined with inherited updating methods private int progressValue; @@ -75,7 +78,7 @@ public static int Main(string[] args) { var verboseOption = new Option(new []{"-v", "--verbose"}, "Detailed logs"); - var dataFileOption = new Argument("datafile", "Path to the data.win/.ios/.droid/.unx file"); + var dataFileArgument = new Argument("datafile", "Path to the data.win/.ios/.droid/.unx file"); // Setup new command Command newCommand = new Command("new", "Generates a blank data file") @@ -88,12 +91,13 @@ public static int Main(string[] args) newCommand.Handler = CommandHandler.Create(Program.New); // Setup load command - var scriptRunnerOption = new Option(new []{ "-s", "--scripts"}, "Scripts to apply to the . ex. a.csx b.csx"); - Command loadCommand = new Command("load", "Load a data file and perform actions on it") { - dataFileOption, + var scriptRunnerOption = new Option(new []{ "-s", "--scripts"}, "Scripts to apply to the . Ex. a.csx b.csx"); + Command loadCommand = new Command("load", "Load a data file and perform actions on it") + { + dataFileArgument, scriptRunnerOption, verboseOption, - //TODO: why no force overwrite here, but neded for new? + //TODO: why no force overwrite here, but needed for new? new Option(new []{"-o", "--output"}, "Where to save the modified data file"), new Option(new []{"-l","--line"}, "Run C# string. Runs AFTER everything else"), new Option(new []{"-i", "--interactive"}, "Interactive menu launch") @@ -103,17 +107,31 @@ public static int Main(string[] args) // Setup info command Command infoCommand = new Command("info", "Show basic info about the game data file") { - dataFileOption, + dataFileArgument, verboseOption }; infoCommand.Handler = CommandHandler.Create(Program.Info); + // Setup dump command + Command dumpCommand = new Command("dump", "Dump certain properties about the game data file") + { + dataFileArgument, + verboseOption, + new Option(new []{"-o", "--output"}, "Where to dump data file properties to. Will default to path of the data file"), + new Option(new[] {"-c", "--code"}, + "The code files to dump. Ex. gml_Script_init_map gml_Script_reset_map. Specify 'UMT_DUMP_ALL' to dump all code entries"), + new Option(new[] {"-s", "--strings"}, "Whether to dump all strings"), + new Option(new[] {"-t", "--textures"}, "Whether to dump all embedded textures") + }; + dumpCommand.Handler = CommandHandler.Create(Program.Dump); + // Merge everything together RootCommand rootCommand = new RootCommand { newCommand, loadCommand, - infoCommand + infoCommand, + dumpCommand }; rootCommand.Description = "CLI tool for modding, decompiling and unpacking Undertale (and other Game Maker: Studio games)!"; Parser commandLine = new CommandLineBuilder(rootCommand) @@ -155,9 +173,15 @@ public Program(FileInfo datafile, FileInfo[]? scripts, FileInfo? output, bool ve .WithEmitDebugInformation(true); } - public Program(FileInfo datafile, bool verbose) + public Program(FileInfo datafile, bool verbose, DirectoryInfo output = null) { + Console.WriteLine($"Trying to load file: '{datafile.FullName}'"); + this.Verbose = verbose; this.Data = ReadDataFile(datafile, verbose ? WarningHandler : null, verbose ? MessageHandler : null); + this.Output = output ?? new DirectoryInfo(datafile.DirectoryName); + + if (this.Verbose) + Console.WriteLine("Output directory has been set to " + this.Output.FullName); } /// @@ -270,6 +294,61 @@ private static int Info(InfoOptions options) return EXIT_SUCCESS; } + /// + /// Method that gets executed on the "dump" command + /// + /// The arguments that have been provided with the "dump" command + /// and for being successful and failing respectively + private static int Dump(DumpOptions options) + { + //TODO: get rid of a ton of string concat + + Program program; + try + { + program = new Program(options.Datafile, options.Verbose, options.Output); + } + catch (FileNotFoundException e) + { + Console.Error.WriteLine(e.Message); + return EXIT_FAILURE; + } + + // If user provided code to dump, dump code + if ((options.Code != null) && (options.Code.Length > 0) && (program.Data.Code.Count > 0)) + { + // If user wanted to dump everything, do that, otherwise only dump what user provided + if (options.Code.Contains("UMT_DUMP_ALL")) + { + foreach (UndertaleCode code in program.Data.Code) + { + program.DumpCodeEntry(code.Name.Content); + } + } + else + { + foreach (string code in options.Code) + { + program.DumpCodeEntry(code); + } + } + } + + // If user wanted to dump strings, dump all of them in a text file + if (options.Strings) + { + program.DumpStrings(); + } + + // If user wanted to dump embedded textures, dump all of them + if (options.Textures) + { + program.DumpTextures(); + } + + return EXIT_SUCCESS; + } + /// /// Runs the interactive menu indefinitely until user quits out of it. /// @@ -389,6 +468,70 @@ private void CliQuickInfo() if (IsInteractive) Pause(); } + /// + /// Dumped a code entry from a data file. + /// + /// The code entry that should get dumped + private void DumpCodeEntry(string codeEntry) + { + UndertaleCode? code = Data.Code.FirstOrDefault(c => c.Name.Content == codeEntry); + + if (code == null) + { + Console.Error.WriteLine($"Data file does not contain a code entry named {codeEntry}!"); + return; + } + + string directory = Output.FullName + "/CodeEntries/"; + + Directory.CreateDirectory(directory); + + if (Verbose) + Console.WriteLine("Dumping " + codeEntry); + + File.WriteAllText(directory + "/" + codeEntry + ".gml", GetDecompiledText(code)); + } + + /// + /// Dumps all strings in a data file. + /// + private void DumpStrings() + { + string directory = Output.FullName; + + Directory.CreateDirectory(directory); + + StringBuilder combinedText = new StringBuilder(); + foreach (UndertaleString dataString in Data.Strings) + { + if (Verbose) + Console.WriteLine("Added " + dataString.Content); + combinedText.Append(dataString.Content + "\n"); + } + + if (Verbose) + Console.WriteLine("Writing all strings to disk"); + File.WriteAllText(directory + "/strings.txt", combinedText.ToString()); + } + + /// + /// Dumps all embedded textures in a data file. + /// + private void DumpTextures() + { + string directory = Output.FullName + "/EmbeddedTextures/"; + + Directory.CreateDirectory(directory); + + foreach (UndertaleEmbeddedTexture texture in Data.EmbeddedTextures) + { + if (Verbose) + Console.WriteLine("Dumping " + texture.Name); + File.WriteAllBytes(directory + "/" + texture.Name.Content + ".png", texture.TextureData.TextureBlob); + } + } + + /// /// Evaluates and executes the contents of a file as C# Code. /// From 2628754737b4bbdfa734654be9b6901123062d89 Mon Sep 17 00:00:00 2001 From: Miepee <38186597+Miepee@users.noreply.github.com> Date: Thu, 21 Apr 2022 18:25:20 +0200 Subject: [PATCH 2/3] CLI: initial replacing support --- UndertaleModCli/CommandOptions.cs | 36 ++++- UndertaleModCli/Program.cs | 177 +++++++++++++++++++++++- UndertaleModLib/Models/UndertaleCode.cs | 1 + 3 files changed, 209 insertions(+), 5 deletions(-) diff --git a/UndertaleModCli/CommandOptions.cs b/UndertaleModCli/CommandOptions.cs index 357c8fab5..72f0ff55f 100644 --- a/UndertaleModCli/CommandOptions.cs +++ b/UndertaleModCli/CommandOptions.cs @@ -1,7 +1,10 @@ +using System.Collections.Generic; using System.IO; namespace UndertaleModCli { + //TODO: split these all up into individual files + /// /// Cli options for the New command /// @@ -81,7 +84,7 @@ public class InfoOptions } /// - /// Cli options for the Info command + /// Cli options for the Dump command /// public class DumpOptions { @@ -115,4 +118,35 @@ public class DumpOptions /// public bool Textures { get; set; } } + + /// + /// Cli options for the Replace command + /// + public class ReplaceOptions + { + /// + /// File path to the data file + /// + public FileInfo Datafile { get; set; } + + /// + /// File path to where to save the modified data file + /// + public FileInfo? Output { get; set; } + + /// + /// Determines if Cli should print out verbose logs + /// + public bool Verbose { get; set; } = false; + + /// + /// Equal separated values of code entry and the file to replace the code entry with. + /// + public string[] Code { get; set; } + + /// + /// Equal separated values of embedded texture and the file to replace the embedded texture with. + /// + public string[] Textures { get; set; } + } } \ No newline at end of file diff --git a/UndertaleModCli/Program.cs b/UndertaleModCli/Program.cs index 523a13232..d4f345a35 100644 --- a/UndertaleModCli/Program.cs +++ b/UndertaleModCli/Program.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.Scripting; using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.IO; using System.Reflection; using UndertaleModLib; @@ -49,6 +50,16 @@ public partial class Program : IScriptInterface /// private FileSystemInfo? Output { get; } + /// + /// Constant, used to indicate that the user wants to replace everything in a replace command. + /// + private const string UMT_REPLACE_ALL = "UMT_REPLACE_ALL"; + + /// + /// Constant, used to indicate that the user wants to dump everything in a dump command + /// + private const string UMT_DUMP_ALL = "UMT_DUMP_ALL"; + //TODO: document these, these are intertwined with inherited updating methods private int progressValue; private Task updater; @@ -119,19 +130,33 @@ public static int Main(string[] args) verboseOption, new Option(new []{"-o", "--output"}, "Where to dump data file properties to. Will default to path of the data file"), new Option(new[] {"-c", "--code"}, - "The code files to dump. Ex. gml_Script_init_map gml_Script_reset_map. Specify 'UMT_DUMP_ALL' to dump all code entries"), + $"The code files to dump. Ex. gml_Script_init_map gml_Script_reset_map. Specify '{UMT_DUMP_ALL}' to dump all code entries"), new Option(new[] {"-s", "--strings"}, "Whether to dump all strings"), new Option(new[] {"-t", "--textures"}, "Whether to dump all embedded textures") }; dumpCommand.Handler = CommandHandler.Create(Program.Dump); + // Setup replace command + Command replaceCommand = new Command("replace", "Replace certain properties in the game data file") + { + dataFileArgument, + verboseOption, + new Option(new []{"-o", "--output"}, "Where to save the modified data file"), + new Option(new[] {"-c", "--code"}, + $"Which code files to replace with which file. Ex. 'gml_Script_init_map=./newCode.gml'. It is possible to replace everything by using '{UMT_REPLACE_ALL}'"), + new Option(new []{"-t", "--textures"}, + $"Which embedded texture entry to replace with which file. Ex. 'Texture 0=./newTexture.png'. It is possible to replace everything by using '{UMT_REPLACE_ALL}'") + }; + replaceCommand.Handler = CommandHandler.Create(Program.Replace); + // Merge everything together RootCommand rootCommand = new RootCommand { newCommand, loadCommand, infoCommand, - dumpCommand + dumpCommand, + replaceCommand }; rootCommand.Description = "CLI tool for modding, decompiling and unpacking Undertale (and other Game Maker: Studio games)!"; Parser commandLine = new CommandLineBuilder(rootCommand) @@ -318,7 +343,7 @@ private static int Dump(DumpOptions options) if ((options.Code != null) && (options.Code.Length > 0) && (program.Data.Code.Count > 0)) { // If user wanted to dump everything, do that, otherwise only dump what user provided - if (options.Code.Contains("UMT_DUMP_ALL")) + if (options.Code.Contains(UMT_DUMP_ALL)) { foreach (UndertaleCode code in program.Data.Code) { @@ -349,6 +374,106 @@ private static int Dump(DumpOptions options) return EXIT_SUCCESS; } + /// + /// Method that gets executed on the "replace" command + /// + /// The arguments that have been provided with the "replace" command + /// and for being successful and failing respectively + private static int Replace(ReplaceOptions options) + { + Program program; + try + { + program = new Program(options.Datafile, null, options.Output, options.Verbose); + } + catch (FileNotFoundException e) + { + Console.Error.WriteLine(e.Message); + return EXIT_FAILURE; + } + + // If user provided code to replace, replace it + if ((options.Code != null) && (options.Code.Length > 0) && (program.Data.Code.Count > 0)) + { + // get the values and put them into a dictionary for ease of use + Dictionary codeDict = new Dictionary(); + foreach (string code in options.Code) + { + string[] splitText = code.Split('='); + + if (splitText.Length != 2) + { + Console.Error.WriteLine(code + " is malformed! Should be of format 'name_of_code=./newCode.gml' instead!"); + return EXIT_FAILURE; + } + + codeDict.Add(splitText[0], new FileInfo(splitText[1])); + } + + // If user wants to replace all, we'll be handling it differently + if (codeDict.ContainsKey(UMT_REPLACE_ALL)) + { + string directory = codeDict[UMT_REPLACE_ALL].FullName; + foreach (FileInfo file in new DirectoryInfo(directory).GetFiles()) + { + program.ReplaceCodeEntryWithFile(file.Name, file); + } + } + // Otherwise, just replace every file which was given + else + { + foreach (KeyValuePair keyValue in codeDict) + { + program.ReplaceCodeEntryWithFile(keyValue.Key, keyValue.Value); + } + } + + } + + // If user provided texture to replace, replace it + if ((options.Textures != null) && (options.Textures.Length > 0)) + { + // get the values and put them into a dictionary for ease of use + Dictionary textureDict = new Dictionary(); + foreach (string texture in options.Textures) + { + string[] splitText = texture.Split('='); + + if (splitText.Length != 2) + { + Console.Error.WriteLine(texture + " is malformed! Should be of format 'Name=./new.png' instead!"); + return EXIT_FAILURE; + } + + textureDict.Add(splitText[0], new FileInfo(splitText[1])); + } + + // If user wants to replace all, we'll be handling it differently + if (textureDict.ContainsKey(UMT_REPLACE_ALL)) + { + string directory = textureDict[UMT_REPLACE_ALL].FullName; + foreach (FileInfo file in new DirectoryInfo(directory).GetFiles()) + { + program.ReplaceTextureWithFile(file.Name, file); + } + } + // Otherwise, just replace every file which was given + else + { + foreach (KeyValuePair keyValue in textureDict) + { + program.ReplaceTextureWithFile(keyValue.Key, keyValue.Value); + } + } + } + + // if parameter to save file was given, save the data file + if (options.Output != null) + program.SaveDataFile(options.Output.FullName); + + return EXIT_SUCCESS; + } + /// /// Runs the interactive menu indefinitely until user quits out of it. /// @@ -531,6 +656,47 @@ private void DumpTextures() } } + /// + /// Replaces a code entry with text from another file. + /// + /// The code entry to replace + /// File path which should replace the code entry. + private void ReplaceCodeEntryWithFile(string codeEntry, FileInfo fileToReplace) + { + UndertaleCode? code = Data.Code.FirstOrDefault(c => c.Name.Content == codeEntry); + + if (code == null) + { + Console.Error.WriteLine($"Data file does not contain a code entry named {codeEntry}!"); + return; + } + + if (Verbose) + Console.WriteLine("Replacing " + codeEntry); + + ImportGMLString(codeEntry, File.ReadAllText(fileToReplace.FullName)); + } + + /// + /// Replaces an embedded texture with contents from another file. + /// + /// Embedded texture to replace + /// File path which should replace the embedded texture. + private void ReplaceTextureWithFile(string textureEntry, FileInfo fileToReplace) + { + UndertaleEmbeddedTexture? texture = Data.EmbeddedTextures.FirstOrDefault(t => t.Name.Content == textureEntry); + + if (texture == null) + { + Console.Error.WriteLine($"Data file does not contain an embedded texture named {textureEntry}!"); + return; + } + + if (Verbose) + Console.WriteLine("Replacing " + textureEntry); + + texture.TextureData.TextureBlob = File.ReadAllBytes(fileToReplace.FullName); + } /// /// Evaluates and executes the contents of a file as C# Code. @@ -619,7 +785,10 @@ private static UndertaleData ReadDataFile(FileInfo datafile, WarningHandlerDeleg try { using FileStream fs = datafile.OpenRead(); - return UndertaleIO.Read(fs, warningHandler, messageHandler); + UndertaleData gmData = UndertaleIO.Read(fs, warningHandler, messageHandler); + //TODO: this should be handled in UTCode, not here + gmData.ToolInfo.AppDataProfiles = ""; + return gmData; } catch (FileNotFoundException e) { diff --git a/UndertaleModLib/Models/UndertaleCode.cs b/UndertaleModLib/Models/UndertaleCode.cs index 02ea9b921..261561148 100644 --- a/UndertaleModLib/Models/UndertaleCode.cs +++ b/UndertaleModLib/Models/UndertaleCode.cs @@ -1257,6 +1257,7 @@ public void ReplaceGML(string gmlCode, UndertaleData data) data.GMLCacheChanged?.Add(Name.Content); + //TODO: only do this if profile mode is enabled in the first place try { // When necessary, write to profile. From ec75760adfcf8cd53e3b31c098a897463e1e5043 Mon Sep 17 00:00:00 2001 From: Miepee <38186597+Miepee@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:46:47 +0200 Subject: [PATCH 3/3] Cli: some cleanup of dump+replace --- UndertaleModCli/Program.cs | 78 ++++++++++++++------------------------ 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/UndertaleModCli/Program.cs b/UndertaleModCli/Program.cs index d4f345a35..df85c2716 100644 --- a/UndertaleModCli/Program.cs +++ b/UndertaleModCli/Program.cs @@ -326,8 +326,6 @@ private static int Info(InfoOptions options) /// and for being successful and failing respectively private static int Dump(DumpOptions options) { - //TODO: get rid of a ton of string concat - Program program; try { @@ -343,33 +341,23 @@ private static int Dump(DumpOptions options) if ((options.Code != null) && (options.Code.Length > 0) && (program.Data.Code.Count > 0)) { // If user wanted to dump everything, do that, otherwise only dump what user provided + string[] codeArray; if (options.Code.Contains(UMT_DUMP_ALL)) - { - foreach (UndertaleCode code in program.Data.Code) - { - program.DumpCodeEntry(code.Name.Content); - } - } + codeArray = program.Data.Code.Select(c => c.Name.Content).ToArray(); else - { - foreach (string code in options.Code) - { - program.DumpCodeEntry(code); - } - } + codeArray = options.Code; + + foreach (string code in codeArray) + program.DumpCodeEntry(code); } // If user wanted to dump strings, dump all of them in a text file if (options.Strings) - { - program.DumpStrings(); - } + program.DumpAllStrings(); // If user wanted to dump embedded textures, dump all of them if (options.Textures) - { - program.DumpTextures(); - } + program.DumpAllTextures(); return EXIT_SUCCESS; } @@ -392,7 +380,7 @@ private static int Replace(ReplaceOptions options) return EXIT_FAILURE; } - // If user provided code to replace, replace it + // If user provided code to replace, replace them if ((options.Code != null) && (options.Code.Length > 0) && (program.Data.Code.Count > 0)) { // get the values and put them into a dictionary for ease of use @@ -403,34 +391,29 @@ private static int Replace(ReplaceOptions options) if (splitText.Length != 2) { - Console.Error.WriteLine(code + " is malformed! Should be of format 'name_of_code=./newCode.gml' instead!"); + Console.Error.WriteLine($"{code} is malformed! Should be of format 'name_of_code=./newCode.gml' instead!"); return EXIT_FAILURE; } codeDict.Add(splitText[0], new FileInfo(splitText[1])); } - // If user wants to replace all, we'll be handling it differently + // If user wants to replace all, we'll be handling it differently. Replace every file from the provided directory if (codeDict.ContainsKey(UMT_REPLACE_ALL)) { string directory = codeDict[UMT_REPLACE_ALL].FullName; foreach (FileInfo file in new DirectoryInfo(directory).GetFiles()) - { program.ReplaceCodeEntryWithFile(file.Name, file); - } } // Otherwise, just replace every file which was given else { foreach (KeyValuePair keyValue in codeDict) - { program.ReplaceCodeEntryWithFile(keyValue.Key, keyValue.Value); - } } - } - // If user provided texture to replace, replace it + // If user provided texture to replace, replace them if ((options.Textures != null) && (options.Textures.Length > 0)) { // get the values and put them into a dictionary for ease of use @@ -441,29 +424,25 @@ private static int Replace(ReplaceOptions options) if (splitText.Length != 2) { - Console.Error.WriteLine(texture + " is malformed! Should be of format 'Name=./new.png' instead!"); + Console.Error.WriteLine($"{texture} is malformed! Should be of format 'Name=./new.png' instead!"); return EXIT_FAILURE; } textureDict.Add(splitText[0], new FileInfo(splitText[1])); } - // If user wants to replace all, we'll be handling it differently + // If user wants to replace all, we'll be handling it differently. Replace every file from the provided directory if (textureDict.ContainsKey(UMT_REPLACE_ALL)) { string directory = textureDict[UMT_REPLACE_ALL].FullName; foreach (FileInfo file in new DirectoryInfo(directory).GetFiles()) - { program.ReplaceTextureWithFile(file.Name, file); - } } // Otherwise, just replace every file which was given else { - foreach (KeyValuePair keyValue in textureDict) - { - program.ReplaceTextureWithFile(keyValue.Key, keyValue.Value); - } + foreach ((string key, FileInfo value) in textureDict) + program.ReplaceTextureWithFile(key, value); } } @@ -487,6 +466,7 @@ private void RunInteractiveMenu() Console.WriteLine("3 - Save and overwrite."); Console.WriteLine("4 - Save to different place."); Console.WriteLine("5 - Display quick info."); + //TODO: add dumping and replacing options Console.WriteLine("6 - Quit without saving."); Console.Write("Input, please: "); @@ -594,7 +574,7 @@ private void CliQuickInfo() } /// - /// Dumped a code entry from a data file. + /// Dumps a code entry from a data file. /// /// The code entry that should get dumped private void DumpCodeEntry(string codeEntry) @@ -607,20 +587,20 @@ private void DumpCodeEntry(string codeEntry) return; } - string directory = Output.FullName + "/CodeEntries/"; + string directory = $"{Output.FullName}/CodeEntries/"; Directory.CreateDirectory(directory); if (Verbose) - Console.WriteLine("Dumping " + codeEntry); + Console.WriteLine($"Dumping {codeEntry}"); - File.WriteAllText(directory + "/" + codeEntry + ".gml", GetDecompiledText(code)); + File.WriteAllText($"{directory}/{codeEntry}.gml", GetDecompiledText(code)); } /// /// Dumps all strings in a data file. /// - private void DumpStrings() + private void DumpAllStrings() { string directory = Output.FullName; @@ -630,29 +610,29 @@ private void DumpStrings() foreach (UndertaleString dataString in Data.Strings) { if (Verbose) - Console.WriteLine("Added " + dataString.Content); - combinedText.Append(dataString.Content + "\n"); + Console.WriteLine($"Added {dataString.Content}"); + combinedText.Append($"{dataString.Content}\n"); } if (Verbose) Console.WriteLine("Writing all strings to disk"); - File.WriteAllText(directory + "/strings.txt", combinedText.ToString()); + File.WriteAllText($"{directory}/strings.txt", combinedText.ToString()); } /// /// Dumps all embedded textures in a data file. /// - private void DumpTextures() + private void DumpAllTextures() { - string directory = Output.FullName + "/EmbeddedTextures/"; + string directory = $"{Output.FullName}/EmbeddedTextures/"; Directory.CreateDirectory(directory); foreach (UndertaleEmbeddedTexture texture in Data.EmbeddedTextures) { if (Verbose) - Console.WriteLine("Dumping " + texture.Name); - File.WriteAllBytes(directory + "/" + texture.Name.Content + ".png", texture.TextureData.TextureBlob); + Console.WriteLine($"Dumping {texture.Name}"); + File.WriteAllBytes($"{directory}/{texture.Name.Content}.png", texture.TextureData.TextureBlob); } }