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