using System; using System.IO; using System.Threading.Tasks; using System.Threading; using System.Linq; using ICSharpCode.SharpZipLib.Zip; using System.Runtime.Serialization.Formatters.Binary; using System.Security; using System.Runtime.InteropServices; using System.Text; using System.Collections.Generic; using Tools.Crypto; namespace ndview { class Program { static void Usage() { Console.WriteLine("ndview [--deleted-key ] [] "); } static async Task Decrypt(Stream from, Stream keyFile, Func getPasssword, CancellationToken token) { using (var enc = new encaes.AesEncryptor(from) { KeepAlive = true }) { enc.Key = await encaes.AesEncryptor.LoadKey(keyFile, getPasssword, token); var tempFile = new TempFile(); try { await enc.Decrypt(tempFile.Stream, token); return tempFile; } catch (Exception ex) { await tempFile.DisposeAsync(); throw ex; } } } static async Task Extract(Stream from, CancellationToken token) { var td = new TempDirectory(); try { FastZip zip = new FastZip(); await Task.Yield(); token.ThrowIfCancellationRequested(); zip.ExtractZip(from, td.Directory.FullName, FastZip.Overwrite.Always, (x) => true, null, null, false, false); } catch (Exception ex) { td.Dispose(); throw ex; } return td; } static (FileInfo BoardFile, DirectoryInfo ImagesDirectory) select(DirectoryInfo folder) { FileInfo bi = null; DirectoryInfo di = null; foreach (var fi in folder.GetFiles()) { if (fi.Name.EndsWith(".board")) { string nnm = string.Join('.', fi.Name.Split(".")[..^1]); if (Directory.Exists(Path.Combine(folder.FullName, nnm))) { return (fi, new DirectoryInfo(Path.Combine(folder.FullName, nnm))); } } bi = fi; } if (bi == null) throw new InvalidDataException("No board info found."); di = folder.GetDirectories().FirstOrDefault(); if (di == null) throw new InvalidDataException("No images dir found"); return (bi, di); } static async Task generate(Stream bif, DirectoryInfo images, DirectoryInfo output, CancellationToken token) { var binf = new BinaryFormatter(); await Task.Yield(); token.ThrowIfCancellationRequested(); var bi = (napdump.BoardInfo)binf.Deserialize(bif); if (!output.Exists) output.Create(); var gen = new PageGenerator(bi, images); Console.WriteLine($" starting {bi.BoardName} w/ {images.Name} -> {output.FullName}"); await gen.GenerateFull(output, token); } static Task ReadPassword(string prompt = null, CancellationToken token = default) { TaskCompletionSource comp = new TaskCompletionSource(); List pwd = new List(); bool set = false; _ = Task.Run(() => { if (prompt != null) Console.Write(prompt); try { using var _c = token.Register(() => { if (!set) { set = true; comp.SetException(new OperationCanceledException()); } }); while (true) { ConsoleKeyInfo i = Console.ReadKey(true); if (token.IsCancellationRequested) break; if (i.Key == ConsoleKey.Enter) { Console.WriteLine(); break; } else if (i.Key == ConsoleKey.Backspace) { if (pwd.Count > 0) { pwd.RemoveAt(pwd.Count - 1); } } else if (i.KeyChar != '\u0000') { pwd.Add(i.KeyChar); } } if (!set) { set = true; if (token.IsCancellationRequested) comp.SetException(new OperationCanceledException()); else comp.SetResult(new string(pwd.ToArray())); } } catch (Exception ex) { Console.WriteLine(); pwd.Clear(); if (!set) { set = true; comp.SetException(ex); } } }); return comp.Task; } static readonly CancellationTokenSource cancel = new CancellationTokenSource(); public static AESKey? DeletedKey { get; private set; } = null; static async Task loadAesKey(string from) { using (var fs = new FileStream(from, FileMode.Open, FileAccess.Read)) { DeletedKey = await encaes.AesEncryptor.LoadKey(fs, async () => await encaes.AesEncryptor.ReadPassword("Key is password protected: ")); } } static Task parseArgs(ref string[] args) { string delk = null; for(int i=0;i j != i && j != (i + 1)).ToArray(); Console.WriteLine("Set deleted key to "+delk); } } if (delk == null) return Task.CompletedTask; else { return loadAesKey(delk); } } static async Task Main(string[] args) { if (args.Length < 2) { Usage(); return; } else { try { await parseArgs(ref args); } catch (Exception ex) { Console.WriteLine("Failed to load deleted key: " + ex.Message); return; } if (File.Exists(args[0])) { Console.CancelKeyPress += (o, e) => { if (!cancel.IsCancellationRequested) { cancel.Cancel(); e.Cancel = true; } }; try { if (await encaes.AesEncryptor.IsEncryptedFile(args[0], cancel.Token)) { //decrypt & extract if (args.Length < 3) { Usage(); return; } TempFile tf = null; try { Console.WriteLine("Decrypting..."); using (var inputFile = new FileStream(args[0], FileMode.Open, FileAccess.Read)) { using (var keyStream = new FileStream(args[1], FileMode.Open, FileAccess.Read)) { tf = await Decrypt(inputFile, keyStream, () => { var tsk = ReadPassword("Password: ", cancel.Token); tsk.Wait(); cancel.Token.ThrowIfCancellationRequested(); if (tsk.IsFaulted) throw tsk.Exception; return tsk.Result; }, cancel.Token); } } tf.Stream.Position = 0; Console.WriteLine("Extracting..."); using (var tempd = await Extract(tf.Stream, cancel.Token)) { Console.WriteLine("Selecting best matches"); var (boardFile, imagesDir) = select(tempd.Directory); Console.WriteLine($"Begining generate for {boardFile.Name} with {imagesDir.Name}"); using (var fs = new FileStream(boardFile.FullName, FileMode.Open, FileAccess.Read)) await generate(fs, imagesDir, new DirectoryInfo(args[2]), cancel.Token); Console.WriteLine("Complete"); } } finally { tf?.Dispose(); } } else { //extract TempDirectory tempd; using (var inputFile = new FileStream(args[0], FileMode.Open, FileAccess.Read)) { Console.WriteLine("Extracting..."); tempd = await Extract(inputFile, cancel.Token); } try { Console.WriteLine("Selecting best matches"); var (boardFile, imagesDir) = select(tempd.Directory); Console.WriteLine($"Begining generate for {boardFile.Name} with {imagesDir.Name}"); using (var fs = new FileStream(boardFile.FullName, FileMode.Open, FileAccess.Read)) await generate(fs, imagesDir, new DirectoryInfo(args[1]), cancel.Token); Console.WriteLine("Complete"); } finally { tempd.Dispose(); } } } catch (OperationCanceledException) { } catch (Exception ex) { Console.WriteLine($"Error ({ex.GetType().Name}) in generation: {ex.Message}"); Console.WriteLine(ex.StackTrace); } } else Console.WriteLine("Input archive must exist."); } cancel.Dispose(); } } }