You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

303 lines
12 KiB

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 <deleted key>] <archive> [<key file>] <output directory>");
}
static async Task<TempFile> Decrypt(Stream from, Stream keyFile, Func<string> 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<TempDirectory> 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<string> ReadPassword(string prompt = null, CancellationToken token = default)
{
TaskCompletionSource<string> comp = new TaskCompletionSource<string>();
List<char> pwd = new List<char>();
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<args.Length-1;i++)
{
if(args[i].ToLower() == "--deleted-key")
{
delk = args[i + 1];
args = args.Where((_, j) => j != i && j != (i + 1)).ToArray();
}
}
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}");
}
}
else Console.WriteLine("Input archive must exist.");
}
cancel.Dispose();
}
}
}