From e1ae2e1ec49867cefcddbec898016bda640e0719 Mon Sep 17 00:00:00 2001 From: dragon Date: Fri, 21 Oct 2022 02:35:26 +0200 Subject: [PATCH] separated concerns --- ExeToBat/BatGen.cs | 720 --------------------------------------- ExeToBat/Console.cs | 362 ++++++++++++++++++++ ExeToBat/ConsoleUtils.cs | 331 ++++++++++++++++++ ExeToBat/ExeToBat.csproj | 4 +- ExeToBat/Generator.cs | 278 +++++++++++++++ 5 files changed, 974 insertions(+), 721 deletions(-) delete mode 100644 ExeToBat/BatGen.cs create mode 100644 ExeToBat/Console.cs create mode 100644 ExeToBat/ConsoleUtils.cs create mode 100644 ExeToBat/Generator.cs diff --git a/ExeToBat/BatGen.cs b/ExeToBat/BatGen.cs deleted file mode 100644 index 71d65da..0000000 --- a/ExeToBat/BatGen.cs +++ /dev/null @@ -1,720 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Linq; - -namespace ExeToBat -{ - static class Generator - { - public const int chunk = 8000; - - public static List Sources = new List(); - - static void Main() => MainMenu(); - - static void MainMenu() - { - Dictionary options = new Dictionary { - { "Files", ChooseSource }, - { "Generate", () => BuildBat(Sources, "output.bat") } - }; - - new ListMenu(options.Keys.ToList()) - { - DisplayTitle = (List Options) => Console.WriteLine("ExeToBat > Main"), - DisplayEntry = (List Options, int index, int i) => - { - Console.WriteLine("[{0}] {1}", i, Options[index]); - }, - HandleEntry = (List Options, int index) => - { - if (options.ContainsKey(Options[index])) - { - options[Options[index]](); - } - else - { - ResetInput(); - } - - return false; - }, - ExitEntry = "Exit", - - }.Show(); - } - - static void ChooseSource() - { - new ListMenu(Sources) - { - DisplayTitle = (List sources) => - { - Console.WriteLine("ExeToBat > Main > Files"); - Console.WriteLine("[{0}] ({1})", Convert.ToString(0).PadLeft(Convert.ToString(sources.Count).Length, ' '), "Add Files"); - }, - DisplayEntry = (List sources, int index, int i) => - Console.WriteLine("[{0}] {1}", - Convert.ToString(i).PadLeft( - Convert.ToString(sources.Count).Length, ' '), - Path.GetFileName(sources[index].File)), - ZeroEntry = (List sources) => - { - AddSource(); - return false; - }, - HandleEntry = (List sources, int index) => - { - ManageSource(sources[index]); - return false; - }, - RefreshEntries = (List sources) => Sources, - - }.Show(); - } - - static void AddSource() - { - bool IsInputValid = false; - while (!IsInputValid) - { - - Console.Clear(); - Console.WriteLine("ExeToBat > Main > Files > Add"); - Console.Write("\n"); - Console.Write("{0}> ", "File/Folder"); - string input = Console.ReadLine(); - - input.Trim(); - input = input.Replace("\"", ""); - if (!string.IsNullOrEmpty(input)) - { - switch (input) - { - case var i when Directory.Exists(i): - IsInputValid = true; - foreach (string file in Directory.GetFiles(input)) - { - Sources.Add(new SourceFile(file)); - } - break; - - case var i when File.Exists(i): - IsInputValid = true; - Sources.Add(new SourceFile(input)); - break; - - default: - ResetInput(); - break; - } - } - else - { - IsInputValid = true; - } - } - } - - static void ManageSource(SourceFile source) - { - Dictionary> options = new Dictionary> - { - {"Edit", () => { - ModifySource(source); - return false; - } - }, - { "Position", () => { - EditPosition(source); - return false; - } - }, - { "Delete", () => { - Sources.Remove(source); - return true; - } - }, - }; - - new ListMenu(options.Keys.ToList()) - { - DisplayTitle = (List Options) => Console.WriteLine("ExeToBat > Main > Files > {0}", Path.GetFileName(source.File)), - DisplayEntry = (List Options, int index, int i) => Console.WriteLine("[{0}] {1}", i, Options[index]), - HandleEntry = (List Options, int index) => - { - if (options.ContainsKey(Options[index])) - { - return options[Options[index]](); - } - else - { - ResetInput(); - } - - return false; - }, - - }.Show(); - } - - static void ModifySource(SourceFile source) - { - - // this could be solved better with an enum - - Dictionary options() - { - Dictionary opts = new Dictionary { - {"File", () => { } }, - {"Extraction directory", () => EditExtraction(source)}, - {"Execute after extraction", () => source.Execute = !source.Execute }, - }; - if (source.Execute) - { - opts.Add("Parameters", () => EditParameters(source)); - opts.Add("Wait for exit", () => source.Wait = !source.Wait); - } - if (source.Execute && source.Wait) { opts.Add("Delete after execution", () => source.Delete = !source.Delete); } - return opts; - } - - Dictionary ValueMap = new Dictionary - { - { "File", "File" }, - { "Extraction directory", "Directory" }, - { "Execute after extraction", "Execute" }, - { "Parameters", "Parameters" }, - { "Wait for exit", "Wait"}, - { "Delete after execution", "Delete"}, - }; - - new ListMenu(options().Keys.ToList()) - { - DisplayTitle = (List Options) => Console.WriteLine("ExeToBat > Main > Files > {0} > Edit", Path.GetFileName(source.File)), - DisplayEntry = (List Options, int index, int i) => - { - int MaxLength = options().Keys.Select(x => x.Length).Max(); - Console.WriteLine("[{0}] {1} | {2}", i, Options[index].PadRight(MaxLength, ' '), source.GetType().GetProperty(ValueMap[Options[index]]).GetValue(source).ToString()); - }, - HandleEntry = (List Options, int index) => - { - if (options().ContainsKey(Options[index])) - { - options()[Options[index]](); - } - else - { - ResetInput(); - } - - return false; - }, - RefreshEntries = (List Options) => options().Keys.ToList(), - - }.Show(); - - } - - static void EditExtraction(SourceFile source) - { - bool IsInputValid = false; - while (!IsInputValid) - { - Console.Clear(); - Console.WriteLine("ExeToBat > Main > Files > {0} > Edit > Extraction", Path.GetFileName(source.File)); - Console.Write("\n"); - Console.WriteLine("Documentation: "); - Console.WriteLine("https://ss64.com/nt/syntax-variables.html"); - Console.WriteLine("https://ss64.com/nt/syntax-args.html"); - Console.Write("\n"); - Console.Write("{0}> ", "Directory"); - string input = Console.ReadLine(); - - input.Trim(); - if (!string.IsNullOrEmpty(input)) - { - source.Directory = input; - IsInputValid = true; - } - else - { - IsInputValid = true; - } - } - } - - static void EditParameters(SourceFile source) - { - bool IsInputValid = false; - while (!IsInputValid) - { - Console.Clear(); - Console.WriteLine("ExeToBat > Main > Files > {0} > Edit > Parameters", Path.GetFileName(source.File)); - Console.Write("\n"); - Console.Write("{0}> ", "Parameters"); - string input = Console.ReadLine(); - - input.Trim(); - if (!string.IsNullOrEmpty(input)) - { - source.Parameters = input; - IsInputValid = true; - } - else - { - IsInputValid = true; - } - } - } - - static void EditPosition(SourceFile source) - { - bool IsInputValid = false; - while (!IsInputValid) - { - Console.Clear(); - Console.WriteLine("ExeToBat > Main > Files > {0} > Position : {1}", Path.GetFileName(source.File), Sources.IndexOf(source)); - - Console.Write("\n"); - Console.Write("{0}> ", "New index"); - string input = Console.ReadLine(); - - if (int.TryParse(input, out int index)) - { - if (index < Sources.Count) - { - Sources.Remove(source); - Sources.Insert(index, source); - IsInputValid = true; - } - else - { - ResetInput(); - } - } - else - { - if (string.IsNullOrEmpty(input)) - { - IsInputValid = true; - } - else - { - ResetInput(); - } - } - } - } - - static void BuildBat(List sources, string outputFile) - { - Console.Clear(); - Console.WriteLine("ExeToBat > Main > Generate"); - - if (Sources.Any()) - { - using (StreamWriter writer = new StreamWriter(outputFile)) - { - Console.WriteLine("[Preparing] basic batch structure..."); - writer.WriteLine("@echo off"); - writer.WriteLine(":: Auto-generated batch file by ExeToBat ::"); - writer.WriteLine(""); - - foreach (SourceFile source in sources) - { - Console.WriteLine("[ Reading ] {0}", source.File); - List fileChunks = Convert.ToBase64String(File.ReadAllBytes(source.File)).Chunks(chunk).ToList(); - string tempFile = Path.Combine("%temp%", source.Resource); - writer.WriteLine("("); - - int pos = 0; - foreach (string part in fileChunks) - { - pos++; - Console.Write("[ Writing ] {0} part {1}/{2}\r", Path.GetFileName(source.File), pos.ToString().PadLeft(fileChunks.Count.ToString().Length, '0'), fileChunks.Count); - writer.WriteLine(string.Format("echo {0}", part)); - } - - Console.WriteLine(); - writer.WriteLine(string.Format(") >> \"{0}\"", tempFile)); - writer.WriteLine(""); - - Console.WriteLine("[ Writing ] decode mechanism"); - writer.WriteLine(string.Format("certutil -decode \"{0}\" \"{1}\" >nul 2>&1", tempFile, Path.Combine(source.Directory, Path.GetFileName(source.File)))); - writer.WriteLine(string.Format("del /f /q \"{0}\" >nul 2>&1", tempFile)); - writer.WriteLine(""); - - if (source.Execute) - { - string wait; - if (source.Wait) { wait = " /wait"; } else { wait = " "; } - Console.WriteLine("[ Writing ] execute mechanism"); - writer.WriteLine(string.Format("start{0} \"\" \"cmd /c {1}\" {2}", wait, Path.Combine(source.Directory, Path.GetFileName(source.File)), source.Parameters)); - if (source.Wait) - { - Console.WriteLine("[ Writing ] wait mechanism"); - if (source.Delete) - { - Console.WriteLine("[ Writing ] delete mechanism"); - writer.WriteLine(string.Format("del /f /q \"{0}\" >nul 2>&1", Path.Combine(source.Directory, Path.GetFileName(source.File)))); - writer.WriteLine(""); - } - } - - writer.WriteLine(""); - - } - - writer.Flush(); - Console.WriteLine("[Generated] {0}", Path.GetFileName(source.File)); - - } - - Console.WriteLine("[Generated] All done"); - - Console.WriteLine("Press anything..."); - Console.ReadKey(); - - } - } - else - { - Console.WriteLine("No files specified"); - Wait(500); - - } - - - } - - public class SourceFile - { - public string File { get; set; } - public bool Execute { get; set; } = false; - public bool Wait { get; set; } = false; - public bool Delete { get; set; } = false; - public string Resource { get; set; } = GenTemp(); - public string Directory { get; set; } = "%~dp0"; - public string Parameters { get; set; } = ""; - - - public SourceFile(string file) - { - File = file; - } - - static public string GenTemp() - { - return string.Format($"res_{new Random().Next(1000, 10000)}.b64"); - } - - } - - static string[] Chunks(this string toSplit, int chunkSize) - { - int stringLength = toSplit.Length; - - int chunksRequired = (int)Math.Ceiling(stringLength / (decimal)chunkSize); - var stringArray = new string[chunksRequired]; - - int lengthRemaining = stringLength; - - for (int i = 0; i < chunksRequired; i++) - { - int lengthToUse = Math.Min(lengthRemaining, chunkSize); - int startIndex = chunkSize * i; - stringArray[i] = toSplit.Substring(startIndex, lengthToUse); - - lengthRemaining -= lengthToUse; - } - - return stringArray; - } - - - - public static void Wait(int ms) - { - using (System.Threading.ManualResetEvent wait = new System.Threading.ManualResetEvent(false)) - { - wait.WaitOne(ms); - } - } - - public class ListMenu - { - - /// - /// CLOE (Command-line List Options Enumerator). - /// Turns a List into a menu of options. Each list item is asigned a number. Provides several callbacks to customise the menu. - /// - /// List of objects you want to display - public ListMenu(List entries) - { - Entries = entries; - } - - /// - /// The prompt that is displayed to the user. - /// - public string Prompt = "Choose"; - - /// - /// The string to be displayed for the option to exit the menu. - /// - public string ExitEntry = "Back"; - - /// - /// The key the user has to press to exit the menu. - /// - public char ExitKey = 'q'; - - /// - /// Wether or not the user can exit the menu. - /// - public bool UserCanExit = true; - - - private List Entries; - - /// - /// The function that processes the chosen menu entries. - /// - public Func, int, bool> HandleEntry = (entries, index) => - { - Console.Clear(); - Console.WriteLine(entries[index]); - Wait(200); - return false; - }; - - /// - /// The function that displays the menu title. - /// - public Action> DisplayTitle = (entries) => { }; - - /// - /// The function that displays the entry to the user. - /// - public Action, int, int> DisplayEntry = (entries, index, num) => - { - Console.WriteLine("[{0}] {1}", Convert.ToString(num).PadLeft(Convert.ToString(entries.Count).Length, ' '), entries[index]); - }; - - /// - /// The function to update the list of entries. - /// - public Func, List> RefreshEntries = (entries) => - { - return entries; - }; - - /// - /// The function that is called when 0th entry in the list is chosen. - /// Display this entry with the title function. - /// - public Func, bool> ZeroEntry = (entries) => - { - ResetInput(); - return false; - }; - - /// - /// Display the menu. - /// - /// - public ListMenu Show() - { - string readInput = string.Empty; - bool MenuExitIsPending = false; - while (!MenuExitIsPending) - { - Console.Clear(); - int printedEntries = 0; - Entries = RefreshEntries(Entries); - DisplayTitle(Entries); - if (Entries.Any()) - { - int num = 0; - foreach (T entry in Entries) - { - num++; - if (string.IsNullOrEmpty(readInput) || Convert.ToString(num).StartsWith(readInput)) - { - DisplayEntry(Entries, Entries.IndexOf(entry), num); - printedEntries++; - } - - if (Entries.Count > Console.WindowHeight - 5) - { - if (printedEntries >= Console.WindowHeight - (5 + 1)) - { - Console.WriteLine("[{0}] +{1}", ".".PadLeft(Convert.ToString(Entries.Count).Length, '.'), Entries.Count); - break; - } - } - else - { - if (printedEntries == Console.WindowHeight - 5) - { - break; - } - } - - } - } - - if (UserCanExit) - { - Console.WriteLine("[{0}] {1}", Convert.ToString(ExitKey).PadLeft(Convert.ToString(Entries.Count).Length, ' '), ExitEntry); - } - - Console.WriteLine(); - - bool InputIsValid = false; - while (!InputIsValid) - { - Console.Write("{0}> {1}", Prompt, readInput); - ConsoleKeyInfo input = Console.ReadKey(); - Wait(20); - int choiceNum = -1; - switch (input) - { - case var key when key.KeyChar.Equals(ExitKey): - if (UserCanExit) - { - Console.WriteLine(); - InputIsValid = true; - MenuExitIsPending = true; - } - else - { - Console.WriteLine(); - ResetInput(); - } - break; - - case var key when key.Key.Equals(ConsoleKey.Backspace): - if (!string.IsNullOrEmpty(readInput)) - { - Console.Write("\b"); - readInput = readInput.Remove(readInput.Length - 1); - } - InputIsValid = true; - break; - - case var key when key.Key.Equals(ConsoleKey.Enter): - if (!string.IsNullOrEmpty(readInput)) - { - if (HandleEntry(Entries, (Convert.ToInt32(readInput) - 1))) - { - MenuExitIsPending = true; - } - readInput = string.Empty; - } - InputIsValid = true; - break; - - case var key when int.TryParse(key.KeyChar.ToString(), out choiceNum): - Console.WriteLine(); - if (string.IsNullOrEmpty(readInput) && choiceNum.Equals(0)) - { - InputIsValid = true; - if (ZeroEntry(Entries)) - { - MenuExitIsPending = true; - } - } - else - { - if (Convert.ToInt32(readInput + Convert.ToString(choiceNum)) <= Entries.Count) - { - InputIsValid = true; - int matchingEntries = 0; - readInput = new System.Text.StringBuilder().Append(readInput).Append(Convert.ToString(choiceNum)).ToString(); - for (int i = 0; i < Entries.Count; i++) - { - if (Convert.ToString(i + 1).StartsWith(readInput) || Convert.ToString(i + 1) == readInput) { matchingEntries++; } - } - if ((readInput.Length == Convert.ToString(Entries.Count).Length) || (matchingEntries == 1)) - { - if (HandleEntry(Entries, (Convert.ToInt32(readInput) - 1))) - { - MenuExitIsPending = true; - } - readInput = string.Empty; - } - } - else - { - ResetInput(); - } - } - break; - - default: - Console.WriteLine(); - ResetInput(); - break; - } - } - } - return this; - } - } - - /// - /// A simple template to create Yes or No menus. - /// - /// The title of the menu. - /// The function to be called upon Yes - /// The function to be called upon No - public static void YesNoMenu(string title, Action Yes, Action No) - { - bool IsInputValid = false; - while (!IsInputValid) - { - Console.Write("{0}? [{1}]> ", title, "Y/N"); - string Input = Console.ReadKey().KeyChar.ToString(); - Wait(20); - Console.Write("\n"); - if (string.Equals(Input, "Y", StringComparison.OrdinalIgnoreCase)) - { - IsInputValid = true; - Yes(); - - } - else if (string.Equals(Input, "N", StringComparison.OrdinalIgnoreCase)) - { - IsInputValid = true; - No(); - } - else - { - ResetInput(); - } - } - } - - public static void ResetInput(string error = "Input Invalid") - { - Console.Write(string.Format("[{0}] {1}", "Error", error)); - Wait(150); - ClearCurrentConsoleLine(); - Console.SetCursorPosition(0, Console.CursorTop - 1); - ClearCurrentConsoleLine(); - } - - public static void ClearCurrentConsoleLine() - { - int currentLineCursor = Console.CursorTop; - Console.SetCursorPosition(0, Console.CursorTop); - Console.Write(new string(' ', Console.BufferWidth)); - Console.SetCursorPosition(0, currentLineCursor); - } - - } - -} diff --git a/ExeToBat/Console.cs b/ExeToBat/Console.cs new file mode 100644 index 0000000..fd1a5f0 --- /dev/null +++ b/ExeToBat/Console.cs @@ -0,0 +1,362 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using static ExeToBat.Generator; +using static System.ConsoleUtils; + +namespace ExeToBat +{ + class Console + { + static void Main() => new Console().MainMenu(); + + public Console() { } + + private readonly Generator generator = new Generator(); + + private void MainMenu() + { + Dictionary options = new Dictionary + { + { "Files", ChooseSource }, + { "Generate", Generate }, + }; + + new ListMenu(options.Keys.ToList()) + { + DisplayTitle = (Options) => System.Console.WriteLine("ExeToBat"), + DisplayEntry = (Options, index, i) => + { + System.Console.WriteLine("[{0}] {1}", i, Options[index]); + }, + HandleEntry = (Options, index) => + { + options[Options[index]](); + return false; + }, + ExitEntry = "Exit", + }.Show(); + } + + private void ChooseSource() + { + new ListMenu(generator.Sources) + { + DisplayTitle = (sources) => + { + System.Console.WriteLine("ExeToBat > Files"); + System.Console.WriteLine( + "[{0}] ({1})", + Convert.ToString(0).PadLeft(Convert.ToString(sources.Count).Length, ' '), + "Add Files" + ); + }, + DisplayEntry = (sources, index, i) => + System.Console.WriteLine( + "[{0}] {1}", + Convert.ToString(i).PadLeft(Convert.ToString(sources.Count).Length, ' '), + Path.GetFileName(sources[index].Path) + ), + ZeroEntry = (sources) => + { + AddSource(); + return false; + }, + HandleEntry = (sources, index) => + { + ManageSource(sources[index]); + return false; + }, + RefreshEntries = (sources) => generator.Sources, + }.Show(); + } + + private void AddSource() + { + bool IsInputValid = false; + while (!IsInputValid) + { + System.Console.Clear(); + System.Console.WriteLine("ExeToBat > Files > Add"); + System.Console.Write("\n"); + System.Console.Write("{0}> ", "File/Folder"); + string input = System.Console.ReadLine(); + + input.Trim(); + input = input.Replace("\"", ""); + if (!string.IsNullOrEmpty(input)) + { + switch (input) + { + case var i when Directory.Exists(i): + IsInputValid = true; + foreach (string file in Directory.GetFiles(input)) + { + generator.Sources.Add(new SourceFile(file)); + } + break; + case var i when File.Exists(i): + IsInputValid = true; + generator.Sources.Add(new SourceFile(input)); + break; + default: + ResetInput(); + break; + } + } + else + { + IsInputValid = true; + } + } + } + + private void ManageSource(SourceFile source) + { + Dictionary> options = new Dictionary> + { + { + "Edit", + () => + { + ModifySource(source); + return false; + } + }, + { + "Position", + () => + { + EditPosition(source); + return false; + } + }, + { + "Delete", + () => + { + generator.Sources.Remove(source); + return true; + } + }, + }; + + new ListMenu(options.Keys.ToList()) + { + DisplayTitle = (Options) => + System.Console.WriteLine( + "ExeToBat > Files > {0}", + Path.GetFileName(source.Path) + ), + DisplayEntry = (Options, index, i) => + System.Console.WriteLine("[{0}] {1}", i, Options[index]), + HandleEntry = (Options, index) => options[Options[index]](), + }.Show(); + } + + private void ModifySource(SourceFile source) + { + List<(string, string, Action)> options() + { + List<(string, string, Action)> result = new List<(string, string, Action)>() + { + ("File", source.Path, () => { }), + ("Extraction directory", source.Directory, () => EditExtraction(source)), + ( + "Execute after extraction", + source.Execute.ToString(), + () => source.Execute = !source.Execute + ), + }; + if (source.Execute) + { + result.Add(("Parameters", source.Parameters, () => EditParameters(source))); + result.Add( + ("Wait for exit", source.Wait.ToString(), () => source.Wait = !source.Wait) + ); + } + if (source.Execute && source.Wait) + { + result.Add( + ( + "Delete after execution", + source.Delete.ToString(), + () => source.Delete = !source.Delete + ) + ); + } + return result; + } + + new ListMenu<(string, string, Action)>(options()) + { + DisplayTitle = (Options) => + System.Console.WriteLine( + "ExeToBat > Files > {0} > Edit", + Path.GetFileName(source.Path) + ), + DisplayEntry = (Options, index, i) => + { + int MaxLength = options().ConvertAll(e => e.Item1.Length).Max(); + System.Console.WriteLine( + "[{0}] {1} | {2}", + i, + Options[index].Item1.PadRight(MaxLength, ' '), + Options[index].Item2 + ); + }, + HandleEntry = (Options, index) => + { + Options[index].Item3(); + return false; + }, + RefreshEntries = (Options) => options(), + }.Show(); + } + + private void EditExtraction(SourceFile source) + { + System.Console.Clear(); + System.Console.WriteLine( + "ExeToBat > Files > {0} > Edit > Extraction", + Path.GetFileName(source.Path) + ); + System.Console.WriteLine(); + System.Console.WriteLine("Documentation: "); + System.Console.WriteLine("https://ss64.com/nt/syntax-variables.html"); + System.Console.WriteLine("https://ss64.com/nt/syntax-args.html"); + System.Console.WriteLine(); + System.Console.Write("{0}> ", "Directory"); + string input = System.Console.ReadLine(); + + if (!string.IsNullOrEmpty(input)) + { + source.Directory = input; + } + } + + private void EditParameters(SourceFile source) + { + System.Console.Clear(); + System.Console.WriteLine( + "ExeToBat > Files > {0} > Edit > Parameters", + Path.GetFileName(source.Path) + ); + System.Console.WriteLine(); + System.Console.Write("{0}> ", "Parameters"); + string input = System.Console.ReadLine(); + + input.Trim(); + if (!string.IsNullOrEmpty(input)) + { + source.Parameters = input; + } + } + + private void EditPosition(SourceFile source) + { + bool IsInputValid = false; + while (!IsInputValid) + { + System.Console.Clear(); + System.Console.WriteLine( + "ExeToBat > Files > {0} > Position : {1}", + Path.GetFileName(source.Path), + generator.Sources.IndexOf(source) + ); + + System.Console.WriteLine(); + System.Console.Write("{0}> ", "New index"); + string input = System.Console.ReadLine(); + + if (int.TryParse(input, out int index)) + { + if (index < generator.Sources.Count) + { + generator.Sources.Remove(source); + generator.Sources.Insert(index, source); + IsInputValid = true; + } + else + { + ResetInput(); + } + } + else + { + if (string.IsNullOrEmpty(input)) + { + IsInputValid = true; + } + else + { + ResetInput(); + } + } + } + } + + private void Generate() + { + System.Console.Clear(); + System.Console.WriteLine("ExeToBat > Generate"); + + generator.Generation += OnGenerate; + + generator.Generate("output.bat"); + + generator.Generation -= OnGenerate; + + System.Console.WriteLine("Press anything..."); + System.Console.ReadKey(); + } + + private void OnGenerate(object sender, GeneratorEvent e) + { + switch (e) + { + case GenerationStartEvent s: + System.Console.WriteLine("Starting generation..."); + System.Console.WriteLine("{0} files scheduled", s.Files.Count); + break; + case ReadingFileEvent s: + System.Console.WriteLine("[{0}] Reading file", s.File.Path); + break; + case WritingFilePartEvent s: + System.Console.Write( + "[{0}] writing part {1}/{2}\r", + s.File.Path, + s.Part.ToString().PadLeft(s.Max.ToString().Length, '0'), + s.Max + ); + break; + case WritingFileDecoderEvent s: + System.Console.WriteLine(); + System.Console.WriteLine("[{0}] Writing decode mechanism", s.File.Path); + break; + case WritingFileExecuteEvent s: + System.Console.WriteLine("[{0}] Writing execute mechanism", s.File.Path); + break; + case WritingFileWaitEvent s: + System.Console.WriteLine("[{0}] Writing wait mechanism", s.File.Path); + break; + case WritingFileDeleteEvent s: + System.Console.WriteLine("[{0}] Writing delete mechanism", s.File.Path); + break; + case WritingFileCompleteEvent s: + System.Console.WriteLine("[{0}] Finshed generating!", s.File.Path); + break; + case GenerationCompleteEvent s: + System.Console.WriteLine("Finished generation! Result written to {0}", s.Path); + break; + case GenerationEmptyEvent _: + System.Console.WriteLine("No files specified"); + break; + case GenerationFailedEvent s: + System.Console.Write("Generation failed with: {0}", s.Error.ToString()); + break; + } + } + } +} diff --git a/ExeToBat/ConsoleUtils.cs b/ExeToBat/ConsoleUtils.cs new file mode 100644 index 0000000..745fb30 --- /dev/null +++ b/ExeToBat/ConsoleUtils.cs @@ -0,0 +1,331 @@ +using System.Collections.Generic; +using System.Linq; + +namespace System +{ + public static class ConsoleUtils + { + /// + /// Pauses the thread for a duration. + /// + /// The duration to pause. + public static void Wait(int ms) + { + using (Threading.ManualResetEvent wait = new Threading.ManualResetEvent(false)) + { + wait.WaitOne(ms); + } + } + + /// + /// Clears the current line of the Console. + /// + public static void ClearCurrentConsoleLine() + { + int currentLineCursor = Console.CursorTop; + Console.SetCursorPosition(0, Console.CursorTop); + Console.Write(new string(' ', Console.BufferWidth)); + Console.SetCursorPosition(0, currentLineCursor); + } + + /// + /// Deletes the last line of the console and displays an error for a short duration. + /// + /// The error to display. + public static void ResetInput(string error = "Input Invalid") + { + Console.Write(string.Format("[{0}] {1}", "Error", error)); + Wait(150); + ClearCurrentConsoleLine(); + Console.SetCursorPosition(0, Console.CursorTop - 1); + ClearCurrentConsoleLine(); + } + + /// + /// A simple template to create Yes or No menus. + /// + /// The title of the menu. + /// The function to be called upon Yes + /// The function to be called upon No + public static void YesNoMenu(string title, Action Yes, Action No) + { + bool IsInputValid = false; + while (!IsInputValid) + { + Console.Write("{0}? [{1}]> ", title, "Y/N"); + string Input = Console.ReadKey().KeyChar.ToString(); + Wait(20); + Console.Write("\n"); + if (string.Equals(Input, "Y", StringComparison.OrdinalIgnoreCase)) + { + IsInputValid = true; + Yes(); + } + else if (string.Equals(Input, "N", StringComparison.OrdinalIgnoreCase)) + { + IsInputValid = true; + No(); + } + else + { + ResetInput(); + } + } + } + + public class ListMenu + { + /// + /// Command-line List Options Enumerator + /// Turns a List into a menu of options. Each list item is asigned a number. Provides several callbacks to customise the menu. + /// + /// List of objects you want to display + public ListMenu(List entries) + { + Entries = entries; + } + + /// + /// The prompt that is displayed to the user. + /// + public string Prompt = "Choose"; + + /// + /// The string to be displayed for the option to exit the menu. + /// + public string ExitEntry = "Back"; + + /// + /// The key the user has to press to exit the menu. + /// + public char ExitKey = 'q'; + + /// + /// Wether or not the user can exit the menu. + /// + public bool UserCanExit = true; + + private List Entries; + + /// + /// The function that processes the chosen menu entries. + /// + public Func, int, bool> HandleEntry = (entries, index) => + { + Console.Clear(); + Console.WriteLine(entries[index]); + Wait(200); + return false; + }; + + /// + /// The function that displays the menu title. + /// + public Action> DisplayTitle = (entries) => { }; + + /// + /// The function that displays the entry to the user. + /// + public Action, int, int> DisplayEntry = (entries, index, num) => + { + Console.WriteLine( + "[{0}] {1}", + Convert.ToString(num).PadLeft(Convert.ToString(entries.Count).Length, ' '), + entries[index] + ); + }; + + /// + /// The function to update the list of entries. + /// + public Func, List> RefreshEntries = (entries) => entries; + + /// + /// The function that is called when 0th entry in the list is chosen. + /// Display this entry with the title function. + /// + public Func, bool> ZeroEntry = (entries) => + { + ResetInput(); + return false; + }; + + /// + /// Display the menu. + /// + /// + public ListMenu Show() + { + string readInput = string.Empty; + bool MenuExitIsPending = false; + while (!MenuExitIsPending) + { + Console.Clear(); + int printedEntries = 0; + Entries = RefreshEntries(Entries); + DisplayTitle(Entries); + if (Entries.Any()) + { + int num = 0; + foreach (T entry in Entries) + { + num++; + if ( + string.IsNullOrEmpty(readInput) + || Convert.ToString(num).StartsWith(readInput) + ) + { + DisplayEntry(Entries, Entries.IndexOf(entry), num); + printedEntries++; + } + + if (Entries.Count > Console.WindowHeight - 5) + { + if (printedEntries >= Console.WindowHeight - (5 + 1)) + { + Console.WriteLine( + "[{0}] +{1}", + ".".PadLeft(Convert.ToString(Entries.Count).Length, '.'), + Entries.Count + ); + break; + } + } + else + { + if (printedEntries == Console.WindowHeight - 5) + { + break; + } + } + } + } + + if (UserCanExit) + { + Console.WriteLine( + "[{0}] {1}", + Convert + .ToString(ExitKey) + .PadLeft(Convert.ToString(Entries.Count).Length, ' '), + ExitEntry + ); + } + + Console.WriteLine(); + + bool InputIsValid = false; + while (!InputIsValid) + { + Console.Write("{0}> {1}", Prompt, readInput); + ConsoleKeyInfo input = Console.ReadKey(); + Wait(20); + int choiceNum = -1; + switch (input) + { + case var key when key.KeyChar.Equals(ExitKey): + if (UserCanExit) + { + Console.WriteLine(); + InputIsValid = true; + MenuExitIsPending = true; + } + else + { + Console.WriteLine(); + ResetInput(); + } + break; + + case var key when key.Key.Equals(ConsoleKey.Backspace): + if (!string.IsNullOrEmpty(readInput)) + { + Console.Write("\b"); + readInput = readInput.Remove(readInput.Length - 1); + } + InputIsValid = true; + break; + + case var key when key.Key.Equals(ConsoleKey.Enter): + if (!string.IsNullOrEmpty(readInput)) + { + if (HandleEntry(Entries, (Convert.ToInt32(readInput) - 1))) + { + MenuExitIsPending = true; + } + readInput = string.Empty; + } + InputIsValid = true; + break; + + case var key when int.TryParse(key.KeyChar.ToString(), out choiceNum): + Console.WriteLine(); + if (string.IsNullOrEmpty(readInput) && choiceNum.Equals(0)) + { + InputIsValid = true; + if (ZeroEntry(Entries)) + { + MenuExitIsPending = true; + } + } + else + { + if ( + Convert.ToInt32(readInput + Convert.ToString(choiceNum)) + <= Entries.Count + ) + { + InputIsValid = true; + int matchingEntries = 0; + readInput = new System.Text.StringBuilder() + .Append(readInput) + .Append(Convert.ToString(choiceNum)) + .ToString(); + for (int i = 0; i < Entries.Count; i++) + { + if ( + Convert.ToString(i + 1).StartsWith(readInput) + || Convert.ToString(i + 1) == readInput + ) + { + matchingEntries++; + } + } + if ( + ( + readInput.Length + == Convert.ToString(Entries.Count).Length + ) || (matchingEntries == 1) + ) + { + if ( + HandleEntry( + Entries, + (Convert.ToInt32(readInput) - 1) + ) + ) + { + MenuExitIsPending = true; + } + readInput = string.Empty; + } + } + else + { + ResetInput(); + } + } + break; + + default: + Console.WriteLine(); + ResetInput(); + break; + } + } + } + return this; + } + } + } +} diff --git a/ExeToBat/ExeToBat.csproj b/ExeToBat/ExeToBat.csproj index 8d949c9..aa9c9c8 100644 --- a/ExeToBat/ExeToBat.csproj +++ b/ExeToBat/ExeToBat.csproj @@ -44,7 +44,9 @@ - + + + diff --git a/ExeToBat/Generator.cs b/ExeToBat/Generator.cs new file mode 100644 index 0000000..a225a3c --- /dev/null +++ b/ExeToBat/Generator.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace ExeToBat +{ + public class Generator + { + public Generator() { } + + public const int ChunkSize = 8000; + + public List Sources = new List(); + + public class SourceFile + { + public SourceFile(string path) + { + Path = path; + } + + public string Path { get; set; } + public bool Execute { get; set; } = false; + public bool Wait { get; set; } = false; + public bool Delete { get; set; } = false; + public string Resource { get; set; } = GetTempFileName(); + public string Directory { get; set; } = "%~dp0"; + public string Parameters { get; set; } = ""; + + static public string GetTempFileName() + { + return string.Format($"res_{new Random().Next(1000, 10000)}.b64"); + } + } + + /// + /// Sends progress updates about ongoing generation task. + /// + public event EventHandler Generation; + + protected virtual void OnGeneration(GeneratorEvent e) + { + EventHandler handler = Generation; + handler?.Invoke(this, e); + } + + /// + /// Generates a batch file with all specified source files. + /// + /// Target output file path. + public void Generate(string outputFile) + { + if (Sources.Any()) + { + using (StreamWriter writer = new StreamWriter(outputFile)) + { + OnGeneration(new GenerationStartEvent(Sources)); + writer.WriteLine("@echo off"); + writer.WriteLine(":: Auto-generated batch file by ExeToBat ::"); + writer.WriteLine(""); + + foreach (SourceFile source in Sources) + { + OnGeneration(new ReadingFileEvent(source)); + List fileChunks = Convert + .ToBase64String(File.ReadAllBytes(source.Path)) + .Chunks(ChunkSize) + .ToList(); + string tempFile = Path.Combine("%temp%", source.Resource); + writer.WriteLine("("); + + int pos = 0; + foreach (string part in fileChunks) + { + pos++; + OnGeneration(new WritingFilePartEvent(source, pos, fileChunks.Count)); + writer.WriteLine(string.Format("echo {0}", part)); + } + + writer.WriteLine(string.Format(") >> \"{0}\"", tempFile)); + writer.WriteLine(""); + + OnGeneration(new WritingFileDecoderEvent(source)); + writer.WriteLine( + string.Format( + "certutil -decode \"{0}\" \"{1}\" >nul 2>&1", + tempFile, + Path.Combine(source.Directory, Path.GetFileName(source.Path)) + ) + ); + writer.WriteLine(string.Format("del /f /q \"{0}\" >nul 2>&1", tempFile)); + writer.WriteLine(""); + + if (source.Execute) + { + string wait; + if (source.Wait) + { + wait = " /wait"; + } + else + { + wait = " "; + } + + OnGeneration(new WritingFileExecuteEvent(source)); + writer.WriteLine( + string.Format( + "start{0} \"\" \"cmd /c {1}\" {2}", + wait, + Path.Combine(source.Directory, Path.GetFileName(source.Path)), + source.Parameters + ) + ); + if (source.Wait) + { + OnGeneration(new WritingFileWaitEvent(source)); + if (source.Delete) + { + OnGeneration(new WritingFileDeleteEvent(source)); + writer.WriteLine( + string.Format( + "del /f /q \"{0}\" >nul 2>&1", + Path.Combine( + source.Directory, + Path.GetFileName(source.Path) + ) + ) + ); + writer.WriteLine(""); + } + } + + writer.WriteLine(""); + } + + writer.Flush(); + OnGeneration(new WritingFileCompleteEvent(source)); + } + + OnGeneration(new GenerationCompleteEvent(outputFile)); + } + } + else + { + OnGeneration(new GenerationEmptyEvent()); + } + } + + public abstract class GeneratorEvent : EventArgs { } + + public class GeneratorFileEvent : GeneratorEvent + { + public SourceFile File { get; protected set; } + } + + public class GenerationStartEvent : GeneratorEvent + { + public GenerationStartEvent(List files) + { + Files = files; + } + + public List Files { get; private set; } + } + + public class ReadingFileEvent : GeneratorFileEvent + { + public ReadingFileEvent(SourceFile file) + { + File = file; + } + } + + public class WritingFilePartEvent : GeneratorFileEvent + { + public WritingFilePartEvent(SourceFile file, int part, int max) + { + File = file; + Part = part; + Max = max; + } + + public int Part { get; private set; } + public int Max { get; private set; } + } + + public class WritingFileDecoderEvent : GeneratorFileEvent + { + public WritingFileDecoderEvent(SourceFile file) + { + File = file; + } + } + + public class WritingFileExecuteEvent : GeneratorFileEvent + { + public WritingFileExecuteEvent(SourceFile file) + { + File = file; + } + } + + public class WritingFileWaitEvent : GeneratorFileEvent + { + public WritingFileWaitEvent(SourceFile file) + { + File = file; + } + } + + public class WritingFileDeleteEvent : GeneratorFileEvent + { + public WritingFileDeleteEvent(SourceFile file) + { + File = file; + } + } + + public class WritingFileCompleteEvent : GeneratorFileEvent + { + public WritingFileCompleteEvent(SourceFile file) + { + File = file; + } + } + + public class GenerationCompleteEvent : GeneratorEvent + { + public GenerationCompleteEvent(string path) + { + Path = path; + } + + public string Path { get; private set; } + } + + public class GenerationEmptyEvent : GeneratorEvent + { + public GenerationEmptyEvent() { } + } + + public class GenerationFailedEvent : GeneratorEvent + { + public GenerationFailedEvent(Exception error) + { + Error = error; + } + + public Exception Error { get; private set; } + } + } + + internal static class StringChunks + { + public static string[] Chunks(this string toSplit, int chunkSize) + { + int stringLength = toSplit.Length; + + int chunksRequired = (int)Math.Ceiling(stringLength / (decimal)chunkSize); + var stringArray = new string[chunksRequired]; + + int lengthRemaining = stringLength; + + for (int i = 0; i < chunksRequired; i++) + { + int lengthToUse = Math.Min(lengthRemaining, chunkSize); + int startIndex = chunkSize * i; + stringArray[i] = toSplit.Substring(startIndex, lengthToUse); + + lengthRemaining -= lengthToUse; + } + + return stringArray; + } + } +}