Browse Source

initial commit

denikson 5 years ago
commit
ada8bee5d5

+ 346 - 0
.gitignore

@@ -0,0 +1,346 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true
+**/wwwroot/lib/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- Backup*.rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/

+ 25 - 0
ArcToolkitCLI.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27428.2043
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcToolkitCLI", "ArcToolkitCLI\ArcToolkitCLI.csproj", "{A653637C-67C1-4CFE-8B16-BB3AC42D0AA1}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{A653637C-67C1-4CFE-8B16-BB3AC42D0AA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A653637C-67C1-4CFE-8B16-BB3AC42D0AA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A653637C-67C1-4CFE-8B16-BB3AC42D0AA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A653637C-67C1-4CFE-8B16-BB3AC42D0AA1}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {F28B4959-3761-4750-AB56-C9EE2B255E1B}
+	EndGlobalSection
+EndGlobal

+ 6 - 0
ArcToolkitCLI/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
+    </startup>
+</configuration>

+ 71 - 0
ArcToolkitCLI/ArcToolkitCLI.csproj

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\ILRepack.2.0.16\build\ILRepack.props" Condition="Exists('..\packages\ILRepack.2.0.16\build\ILRepack.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{A653637C-67C1-4CFE-8B16-BB3AC42D0AA1}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>ArcToolkitCLI</RootNamespace>
+    <AssemblyName>ArcToolkitCLI</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="COM3D2.Toolkit">
+      <HintPath>..\lib\COM3D2.Toolkit.dll</HintPath>
+    </Reference>
+    <Reference Include="CommandLine">
+      <HintPath>..\lib\CommandLine.dll</HintPath>
+    </Reference>
+    <Reference Include="Glob, Version=3.0.0.0, Culture=neutral, PublicKeyToken=2561fd83231d3038, processorArchitecture=MSIL">
+      <HintPath>..\packages\Glob.cs.3.0.27\lib\net40\Glob.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.IO.Abstractions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.IO.Abstractions.3.0.10\lib\net40\System.IO.Abstractions.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\ILRepack.2.0.16\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.2.0.16\build\ILRepack.props'))" />
+    <Error Condition="!Exists('..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets'))" />
+  </Target>
+  <Import Project="..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets" Condition="Exists('..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets')" />
+</Project>

+ 284 - 0
ArcToolkitCLI/Program.cs

@@ -0,0 +1,284 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Abstractions;
+using System.Linq;
+using System.Text;
+using CommandLine;
+using COM3D2.Toolkit.Arc;
+using Ganss.IO;
+
+namespace ArcToolkitCLI
+{
+    internal interface IOptions
+    {
+        [Value(0, MetaName = "input", HelpText = "Input ARC files")]
+        IEnumerable<string> Input { get; set; }
+    }
+
+    internal interface IDecryptionOptions
+    {
+        [Option("arc-search-dir", Required = false, HelpText = "Directory where to search ARC file to decrypt the WARP file")]
+        string ArcDirectory { get; set; }
+
+        [Option('k', "key", HelpText = "Decryption key as a base64 string (applied to all inputs)", Required = false)]
+        string DecryptionKey { get; set; }
+
+        [Option("key-file",
+                HelpText = "A file with decryption keys on each line. Format of the file is <decryption arc name>:<key in base64>.",
+                Required = false)]
+        string KeyFile { get; set; }
+
+        [Option("warc", Required = false, HelpText = "WARC file to use for decryption")]
+        string WarcFile { get; set; }
+    }
+
+    [Verb("extract", HelpText = "Extract the contents of the given ARC files")]
+    internal class ExtractOptions : IOptions, IDecryptionOptions
+    {
+        [Option('o', "output", HelpText = "Output directory", Required = true)]
+        public string Output { get; set; }
+
+        public string ArcDirectory { get; set; }
+        public string DecryptionKey { get; set; }
+        public string KeyFile { get; set; }
+
+        public string WarcFile { get; set; }
+        public IEnumerable<string> Input { get; set; }
+    }
+
+    [Verb("info", HelpText = "Display information about the given ARC files")]
+    internal class InfoOptions : IOptions
+    {
+        [Option("only-key",
+                HelpText = "If the archive is a WARC file, output only the decryption key as a base64 string",
+                Required = false)]
+        public bool OnlyKey { get; set; }
+
+        public IEnumerable<string> Input { get; set; }
+    }
+
+    [Verb("decrypt", HelpText = "Decrypts the provided WARP files")]
+    internal class DecryptOptions : IOptions, IDecryptionOptions
+    {
+        [Option('o', "output", HelpText = "Output directory", Default = ".")]
+        public string Output { get; set; }
+
+        public string ArcDirectory { get; set; }
+        public string DecryptionKey { get; set; }
+        public string KeyFile { get; set; }
+        public string WarcFile { get; set; }
+        public IEnumerable<string> Input { get; set; }
+    }
+
+    internal static class Errors
+    {
+        public enum ErrorCodes
+        {
+            NotAFile = 100,
+            UnknownArcType,
+            KeyNotFound,
+            InvalidKeyFile,
+            NoCorrectKey
+        }
+
+        public static readonly string[] ErrorMessages =
+        {
+                "{0} is not a file",
+                "{0} is not a known ARC file",
+                "No key specified or the key file does not exist",
+                "The provided keyfile is not valid",
+                "The ARC {0} needs a key from {1}"
+        };
+
+        public static int Error(ErrorCodes code, params object[] args)
+        {
+            Console.Error.WriteLine(ErrorMessages[code - ErrorCodes.NotAFile], args);
+            return (int) code;
+        }
+    }
+
+    internal static class Util
+    {
+        private static readonly char[] GlobCharacters = {'*', '{', '}', '[', ']', '?'};
+
+        public static IEnumerable<FileSystemInfoBase> EnumerateFiles(IEnumerable<string> patterns)
+        {
+            foreach (string pattern in patterns)
+                if (IsGlob(pattern))
+                    foreach (var fileSystemInfoBase in Glob.Expand(pattern))
+                        yield return fileSystemInfoBase;
+                else
+                    yield return new FileInfoWrapper(new FileSystem(), new FileInfo(pattern));
+        }
+
+        private static bool IsGlob(string path)
+        {
+            return path.IndexOfAny(GlobCharacters) >= 0;
+        }
+    }
+
+    internal class Program
+    {
+        private static int Main(string[] args)
+        {
+            return Parser.Default.ParseArguments<InfoOptions, ExtractOptions, DecryptOptions>(args)
+                         .MapResult((InfoOptions opts) => DisplayInfo(opts),
+                                    (ExtractOptions opts) => Extract(opts),
+                                    (DecryptOptions opts) => Decrypt(opts),
+                                    errs => 1);
+        }
+
+        private static int DisplayInfo(InfoOptions opts)
+        {
+            bool first = true;
+            foreach (var file in Util.EnumerateFiles(opts.Input))
+            {
+                if (!file.Exists)
+                    return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
+
+                if (!first && !opts.OnlyKey)
+                    Console.WriteLine();
+                first = false;
+
+                using (var stream = File.OpenRead(file.FullName))
+                {
+                    var header = new byte[4];
+                    stream.Read(header, 0, header.Length);
+                    string headerString = Encoding.ASCII.GetString(header);
+                    stream.Position = 0;
+
+                    if (headerString == "warc")
+                    {
+                        var key = new byte[2048];
+                        stream.Read(key, 0, key.Length);
+                        string keyBase64 = Convert.ToBase64String(key);
+                        if (opts.OnlyKey)
+                        {
+                            Console.WriteLine($"{Path.GetFileName(file.FullName)}:{keyBase64}");
+                            continue;
+                        }
+
+                        stream.Position = 0;
+                        using (var warc = new WarcArc(stream))
+                        {
+                            Console.WriteLine($"File name: {file.Name}");
+                            Console.WriteLine("ARC type: WARC");
+                            Console.WriteLine($"ARC Name: {warc.Name}");
+                            Console.WriteLine($"File count: {warc.Entries.Count()}");
+                            Console.WriteLine($"Decryption key: {keyBase64}");
+                        }
+                    }
+                    else if (headerString == "warp")
+                    {
+                        if (opts.OnlyKey)
+                            continue;
+
+                        Console.WriteLine($"File name: {file.Name}");
+                        Console.WriteLine("ARC type: WARP");
+                        using (var br = new BinaryReader(stream))
+                            Console.WriteLine($"Needs a decryption key from the following ARC: {WarpArc.GetKeyWarpName(br)}");
+                    }
+                    else
+                    {
+                        return Errors.Error(Errors.ErrorCodes.UnknownArcType, file.Name);
+                    }
+                }
+            }
+
+            return 0;
+        }
+
+        private static int Extract(ExtractOptions opts)
+        {
+            return 0;
+        }
+
+        private static byte[] ReadKeyFromFile(string filename)
+        {
+            using (var br = new BinaryReader(File.OpenRead(filename)))
+                return br.ReadBytes(2048);
+        }
+
+        private static int Decrypt(DecryptOptions opts)
+        {
+            var keysDict = new Dictionary<string, byte[]>(StringComparer.InvariantCultureIgnoreCase);
+            if (!string.IsNullOrWhiteSpace(opts.KeyFile))
+            {
+                if (!File.Exists(opts.KeyFile))
+                    return Errors.Error(Errors.ErrorCodes.KeyNotFound);
+
+                foreach (string line in File.ReadAllLines(opts.KeyFile))
+                {
+                    var parts = line.Split(':');
+                    if (parts.Length != 2)
+                        return Errors.Error(Errors.ErrorCodes.InvalidKeyFile);
+                    keysDict[parts[0].Trim()] = Convert.FromBase64String(parts[1].Trim());
+                }
+            }
+            else if (!string.IsNullOrWhiteSpace(opts.DecryptionKey))
+            {
+                keysDict["*"] = Convert.FromBase64String(opts.DecryptionKey);
+            }
+            else if (string.IsNullOrWhiteSpace(opts.ArcDirectory) && string.IsNullOrWhiteSpace(opts.WarcFile))
+            {
+                return Errors.Error(Errors.ErrorCodes.KeyNotFound);
+            }
+
+            Directory.CreateDirectory(opts.Output);
+
+            foreach (var file in Util.EnumerateFiles(opts.Input))
+            {
+                if (!file.Exists)
+                    return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
+
+                using (var stream = File.OpenRead(file.FullName))
+                {
+                    var header = new byte[4];
+                    stream.Read(header, 0, header.Length);
+                    string headerString = Encoding.ASCII.GetString(header);
+                    stream.Position = 0;
+                    if (headerString == "warc")
+                    {
+                        Console.WriteLine($"{file.Name} is a WARC file, skipping...");
+                    }
+                    else if (headerString == "warp")
+                    {
+                        try
+                        {
+                            var arcStream = WarpArc.DecryptWarp(stream,
+                                                                requestedFile =>
+                                                                {
+                                                                    if (keysDict.TryGetValue("*", out var key))
+                                                                        return key;
+                                                                    if (keysDict.TryGetValue(requestedFile, out key))
+                                                                        return key;
+                                                                    if (Directory.Exists(opts.ArcDirectory)
+                                                                     && File.Exists(Path.Combine(opts.ArcDirectory, requestedFile)))
+                                                                        return ReadKeyFromFile(Path.Combine(opts.ArcDirectory, requestedFile));
+                                                                    if (File.Exists(opts.WarcFile))
+                                                                        return ReadKeyFromFile(opts.WarcFile);
+                                                                    throw new FileNotFoundException("No key found for the requested ARC",
+                                                                                                    requestedFile);
+                                                                });
+
+                            using (var output =
+                                    File.Create(Path.Combine(Path.GetDirectoryName(opts.Output), Path.GetFileNameWithoutExtension(file.Name))))
+                                arcStream.CopyTo(output);
+                        }
+                        catch (FileNotFoundException fe)
+                        {
+                            return Errors.Error(Errors.ErrorCodes.NoCorrectKey, file.Name, fe.FileName);
+                        }
+                    }
+                    else
+                    {
+                        return Errors.Error(Errors.ErrorCodes.UnknownArcType, file.Name);
+                    }
+                }
+            }
+
+            return 0;
+        }
+    }
+}

+ 36 - 0
ArcToolkitCLI/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ArcToolkitCLI")]
+[assembly: AssemblyDescription("A simple tool to decrypt and extract ARC files")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ArcToolkitCLI")]
+[assembly: AssemblyCopyright("Copyright © Geoffrey Horsington 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("a653637c-67c1-4cfe-8b16-bb3ac42d0aa1")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 7 - 0
ArcToolkitCLI/packages.config

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Glob.cs" version="3.0.27" targetFramework="net461" />
+  <package id="ILRepack" version="2.0.16" targetFramework="net461" />
+  <package id="ILRepack.Lib.MSBuild.Task" version="2.0.16.1" targetFramework="net461" />
+  <package id="System.IO.Abstractions" version="3.0.10" targetFramework="net461" />
+</packages>

BIN
lib/COM3D2.Toolkit.dll


BIN
lib/CommandLine.dll