Program.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.IO.Abstractions;
  5. using System.Linq;
  6. using System.Text;
  7. using CommandLine;
  8. using COM3D2.Toolkit.Arc;
  9. using Ganss.IO;
  10. namespace ArcToolkitCLI
  11. {
  12. internal interface IOptions
  13. {
  14. [Value(0, MetaName = "input", HelpText = "Input ARC files")]
  15. IEnumerable<string> Input { get; set; }
  16. }
  17. internal interface IDecryptionOptions
  18. {
  19. [Option("arc-search-dir", Required = false, HelpText = "Directory where to search ARC file to decrypt the WARP file")]
  20. string ArcDirectory { get; set; }
  21. [Option('k', "key", HelpText = "Decryption key as a base64 string (applied to all inputs)", Required = false)]
  22. string DecryptionKey { get; set; }
  23. [Option("key-file",
  24. HelpText = "A file with decryption keys on each line. Format of the file is <decryption arc name>:<key in base64>.",
  25. Required = false)]
  26. string KeyFile { get; set; }
  27. [Option("warc", Required = false, HelpText = "WARC file to use for decryption")]
  28. string WarcFile { get; set; }
  29. }
  30. [Verb("extract", HelpText = "Extract the contents of the given ARC files")]
  31. internal class ExtractOptions : IOptions, IDecryptionOptions
  32. {
  33. [Option('o', "output", HelpText = "Output directory", Required = true)]
  34. public string Output { get; set; }
  35. public string ArcDirectory { get; set; }
  36. public string DecryptionKey { get; set; }
  37. public string KeyFile { get; set; }
  38. public string WarcFile { get; set; }
  39. public IEnumerable<string> Input { get; set; }
  40. }
  41. [Verb("info", HelpText = "Display information about the given ARC files")]
  42. internal class InfoOptions : IOptions
  43. {
  44. [Option("only-key",
  45. HelpText = "If the archive is a WARC file, output only the decryption key as a base64 string",
  46. Required = false)]
  47. public bool OnlyKey { get; set; }
  48. public IEnumerable<string> Input { get; set; }
  49. }
  50. [Verb("decrypt", HelpText = "Decrypts the provided WARP files")]
  51. internal class DecryptOptions : IOptions, IDecryptionOptions
  52. {
  53. [Option('o', "output", HelpText = "Output directory", Default = ".")]
  54. public string Output { get; set; }
  55. public string ArcDirectory { get; set; }
  56. public string DecryptionKey { get; set; }
  57. public string KeyFile { get; set; }
  58. public string WarcFile { get; set; }
  59. public IEnumerable<string> Input { get; set; }
  60. }
  61. internal static class Errors
  62. {
  63. public enum ErrorCodes
  64. {
  65. NotAFile = 100,
  66. UnknownArcType,
  67. KeyNotFound,
  68. InvalidKeyFile,
  69. NoCorrectKey
  70. }
  71. public static readonly string[] ErrorMessages =
  72. {
  73. "{0} is not a file",
  74. "{0} is not a known ARC file",
  75. "No key specified or the key file does not exist",
  76. "The provided keyfile is not valid",
  77. "The ARC {0} needs a key from {1}"
  78. };
  79. public static int Error(ErrorCodes code, params object[] args)
  80. {
  81. Console.Error.WriteLine(ErrorMessages[code - ErrorCodes.NotAFile], args);
  82. return (int) code;
  83. }
  84. }
  85. internal static class Util
  86. {
  87. private static readonly char[] GlobCharacters = {'*', '{', '}', '[', ']', '?'};
  88. public static IEnumerable<FileSystemInfoBase> EnumerateFiles(IEnumerable<string> patterns)
  89. {
  90. foreach (string pattern in patterns)
  91. if (IsGlob(pattern))
  92. foreach (var fileSystemInfoBase in Glob.Expand(pattern))
  93. yield return fileSystemInfoBase;
  94. else
  95. yield return new FileInfoWrapper(new FileSystem(), new FileInfo(pattern));
  96. }
  97. private static bool IsGlob(string path)
  98. {
  99. return path.IndexOfAny(GlobCharacters) >= 0;
  100. }
  101. }
  102. internal class Program
  103. {
  104. private static int Main(string[] args)
  105. {
  106. return Parser.Default.ParseArguments<InfoOptions, ExtractOptions, DecryptOptions>(args)
  107. .MapResult((InfoOptions opts) => DisplayInfo(opts),
  108. (ExtractOptions opts) => Extract(opts),
  109. (DecryptOptions opts) => Decrypt(opts),
  110. errs => 1);
  111. }
  112. private static int DisplayInfo(InfoOptions opts)
  113. {
  114. bool first = true;
  115. foreach (var file in Util.EnumerateFiles(opts.Input))
  116. {
  117. if (!file.Exists)
  118. return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
  119. if (!first && !opts.OnlyKey)
  120. Console.WriteLine();
  121. first = false;
  122. using (var stream = File.OpenRead(file.FullName))
  123. {
  124. var header = new byte[4];
  125. stream.Read(header, 0, header.Length);
  126. string headerString = Encoding.ASCII.GetString(header);
  127. stream.Position = 0;
  128. if (headerString == "warc")
  129. {
  130. var key = new byte[2048];
  131. stream.Read(key, 0, key.Length);
  132. string keyBase64 = Convert.ToBase64String(key);
  133. if (opts.OnlyKey)
  134. {
  135. Console.WriteLine($"{Path.GetFileName(file.FullName)}:{keyBase64}");
  136. continue;
  137. }
  138. stream.Position = 0;
  139. using (var warc = new WarcArc(stream))
  140. {
  141. Console.WriteLine($"File name: {file.Name}");
  142. Console.WriteLine("ARC type: WARC");
  143. Console.WriteLine($"ARC Name: {warc.Name}");
  144. Console.WriteLine($"File count: {warc.Entries.Count()}");
  145. Console.WriteLine($"Decryption key: {keyBase64}");
  146. }
  147. }
  148. else if (headerString == "warp")
  149. {
  150. if (opts.OnlyKey)
  151. continue;
  152. Console.WriteLine($"File name: {file.Name}");
  153. Console.WriteLine("ARC type: WARP");
  154. using (var br = new BinaryReader(stream))
  155. Console.WriteLine($"Needs a decryption key from the following ARC: {WarpArc.GetKeyWarpName(br)}");
  156. }
  157. else
  158. {
  159. return Errors.Error(Errors.ErrorCodes.UnknownArcType, file.Name);
  160. }
  161. }
  162. }
  163. return 0;
  164. }
  165. private static int Extract(ExtractOptions opts)
  166. {
  167. return 0;
  168. }
  169. private static byte[] ReadKeyFromFile(string filename)
  170. {
  171. using (var br = new BinaryReader(File.OpenRead(filename)))
  172. return br.ReadBytes(2048);
  173. }
  174. private static int Decrypt(DecryptOptions opts)
  175. {
  176. var keysDict = new Dictionary<string, byte[]>(StringComparer.InvariantCultureIgnoreCase);
  177. if (!string.IsNullOrWhiteSpace(opts.KeyFile))
  178. {
  179. if (!File.Exists(opts.KeyFile))
  180. return Errors.Error(Errors.ErrorCodes.KeyNotFound);
  181. foreach (string line in File.ReadAllLines(opts.KeyFile))
  182. {
  183. var parts = line.Split(':');
  184. if (parts.Length != 2)
  185. return Errors.Error(Errors.ErrorCodes.InvalidKeyFile);
  186. keysDict[parts[0].Trim()] = Convert.FromBase64String(parts[1].Trim());
  187. }
  188. }
  189. else if (!string.IsNullOrWhiteSpace(opts.DecryptionKey))
  190. {
  191. keysDict["*"] = Convert.FromBase64String(opts.DecryptionKey);
  192. }
  193. else if (string.IsNullOrWhiteSpace(opts.ArcDirectory) && string.IsNullOrWhiteSpace(opts.WarcFile))
  194. {
  195. return Errors.Error(Errors.ErrorCodes.KeyNotFound);
  196. }
  197. Directory.CreateDirectory(opts.Output);
  198. foreach (var file in Util.EnumerateFiles(opts.Input))
  199. {
  200. if (!file.Exists)
  201. return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
  202. using (var stream = File.OpenRead(file.FullName))
  203. {
  204. var header = new byte[4];
  205. stream.Read(header, 0, header.Length);
  206. string headerString = Encoding.ASCII.GetString(header);
  207. stream.Position = 0;
  208. if (headerString == "warc")
  209. {
  210. Console.WriteLine($"{file.Name} is a WARC file, skipping...");
  211. }
  212. else if (headerString == "warp")
  213. {
  214. try
  215. {
  216. var arcStream = WarpArc.DecryptWarp(stream,
  217. requestedFile =>
  218. {
  219. if (keysDict.TryGetValue("*", out var key))
  220. return key;
  221. if (keysDict.TryGetValue(requestedFile, out key))
  222. return key;
  223. if (Directory.Exists(opts.ArcDirectory)
  224. && File.Exists(Path.Combine(opts.ArcDirectory, requestedFile)))
  225. return ReadKeyFromFile(Path.Combine(opts.ArcDirectory, requestedFile));
  226. if (File.Exists(opts.WarcFile))
  227. return ReadKeyFromFile(opts.WarcFile);
  228. throw new FileNotFoundException("No key found for the requested ARC",
  229. requestedFile);
  230. });
  231. using (var output =
  232. File.Create(Path.Combine(Path.GetDirectoryName(opts.Output), Path.GetFileNameWithoutExtension(file.Name))))
  233. arcStream.CopyTo(output);
  234. }
  235. catch (FileNotFoundException fe)
  236. {
  237. return Errors.Error(Errors.ErrorCodes.NoCorrectKey, file.Name, fe.FileName);
  238. }
  239. }
  240. else
  241. {
  242. return Errors.Error(Errors.ErrorCodes.UnknownArcType, file.Name);
  243. }
  244. }
  245. }
  246. return 0;
  247. }
  248. }
  249. }