Merge branch 'master' into issue-520

This commit is contained in:
Timothy Baldridge 2020-02-14 16:11:59 -07:00 committed by GitHub
commit 109681b2c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 208 additions and 75 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>

View File

@ -113,6 +113,9 @@ namespace Wabbajack.BuildServer.Controllers
continue;
}
if (data is ManualDownloader.State)
continue;
var key = data.PrimaryKeyString;
var found = await Db.DownloadStates.AsQueryable().Where(f => f.Key == key).Take(1).ToListAsync();
if (found.Count > 0)

View File

@ -24,6 +24,9 @@ namespace Wabbajack.BuildServer.Models.Jobs
public override bool UsesNexus { get => Archive.State is NexusDownloader.State; }
public override async Task<JobResult> Execute(DBContext db, SqlService sql, AppSettings settings)
{
if (Archive.State is ManualDownloader.State)
return JobResult.Success();
var pk = new List<object>();
pk.Add(AbstractDownloadState.TypeToName[Archive.State.GetType()]);
pk.AddRange(Archive.State.PrimaryKey);

View File

@ -102,5 +102,6 @@ namespace Wabbajack.Common
public const string MO2ModFolderName = "mods";
public static string PatchCacheFolder => Path.Combine(LocalAppDataPath, "patch_cache");
public static int MaxConnectionsPerServer = 4;
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace Wabbajack.Common.Http
{
public class Client
{
public List<(string, string)> Headers = new List<(string, string)>();
public List<Cookie> Cookies = new List<Cookie>();
public async Task<HttpResponseMessage> GetAsync(string url, HttpCompletionOption responseHeadersRead = HttpCompletionOption.ResponseContentRead)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
foreach (var (k, v) in Headers)
request.Headers.Add(k, v);
return await SendAsync(request, responseHeadersRead);
}
public async Task<string> GetStringAsync(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
foreach (var (k, v) in Headers)
request.Headers.Add(k, v);
if (Cookies.Count > 0)
Cookies.ForEach(c => ClientFactory.Cookies.Add(c));
return await SendStringAsync(request);
}
private async Task<string> SendStringAsync(HttpRequestMessage request)
{
var result = await SendAsync(request);
return await result.Content.ReadAsStringAsync();
}
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage msg, HttpCompletionOption responseHeadersRead = HttpCompletionOption.ResponseContentRead)
{
int retries = 0;
TOP:
try
{
var response = await ClientFactory.Client.SendAsync(msg, responseHeadersRead);
return response;
}
catch (Exception)
{
if (retries > Consts.MaxHTTPRetries) throw;
retries++;
Utils.Log($"Http Connect error to {msg.RequestUri} retry {retries}");
await Task.Delay(100 * retries);
goto TOP;
}
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Net;
using SysHttp = System.Net.Http;
namespace Wabbajack.Common.Http
{
public static class ClientFactory
{
private static SysHttp.SocketsHttpHandler _socketsHandler { get; }
internal static SysHttp.HttpClient Client { get; }
internal static CookieContainer Cookies { get; }
static ClientFactory()
{
Cookies = new CookieContainer();
_socketsHandler = new SysHttp.SocketsHttpHandler
{
CookieContainer = Cookies,
MaxConnectionsPerServer = 8,
PooledConnectionLifetime = TimeSpan.FromSeconds(2),
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(2)
};
Client = new SysHttp.HttpClient(_socketsHandler);
}
}
}

View File

@ -32,7 +32,7 @@ namespace Wabbajack.Common
var tempKey = progIDKey?.OpenSubKey("shell\\open\\command");
if (progIDKey == null || tempKey == null) return true;
var value = tempKey.GetValue("");
return value == null || value.ToString().Equals($"\"{appPath}\" -i=\"%1\"");
return value == null || !value.ToString().Equals($"\"{appPath}\" -i=\"%1\"");
}
public static bool IsAssociated()

View File

@ -53,15 +53,18 @@ namespace Wabbajack.Common
if (!Directory.Exists(Consts.LocalAppDataPath))
Directory.CreateDirectory(Consts.LocalAppDataPath);
if (!Directory.Exists("logs"))
Directory.CreateDirectory("logs");
var programName = Assembly.GetEntryAssembly()?.Location ?? "Wabbajack";
LogFile = Path.GetFileNameWithoutExtension(programName) + ".current.log";
LogFile = Path.Combine("logs", Path.GetFileNameWithoutExtension(programName) + ".current.log");
_startTime = DateTime.Now;
if (LogFile.FileExists())
{
var new_path = Path.GetFileNameWithoutExtension(programName) + (new FileInfo(LogFile)).LastWriteTime.ToString(" yyyy-MM-dd HH_mm_ss") + ".log";
File.Move(LogFile, new_path, MoveOptions.ReplaceExisting);
var newPath = Path.Combine("logs", Path.GetFileNameWithoutExtension(programName) + (new FileInfo(LogFile)).LastWriteTime.ToString(" yyyy-MM-dd HH_mm_ss") + ".log");
File.Move(LogFile, newPath, MoveOptions.ReplaceExisting);
}
var watcher = new FileSystemWatcher(Consts.LocalAppDataPath);

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
@ -40,6 +40,7 @@
<PackageReference Include="ReactiveUI" Version="11.1.23" />
<PackageReference Include="SharpZipLib" Version="1.2.0" />
<PackageReference Include="System.Data.HashFunction.xxHash" Version="2.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.7.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.7.0" />
<PackageReference Include="YamlDotNet" Version="8.1.0" />

View File

@ -22,7 +22,7 @@ namespace Wabbajack.Lib
public bool ReadmeIsWebsite;
public string WabbajackVersion;
protected static string _vfsCacheName => Path.Combine(Consts.LocalAppDataPath, "vfs_compile_cache.bin");
protected string _vfsCacheName => Path.Combine(Consts.LocalAppDataPath, $"vfs_compile_cache_{ModListName?.StringSHA256Hex() ?? "_Unknown_"}.bin");
/// <summary>
/// A stream of tuples of ("Update Title", 0.25) which represent the name of the current task
/// and the current progress.

View File

@ -21,7 +21,7 @@ namespace Wabbajack.Lib.Downloaders
private readonly string _encryptedKeyName;
private readonly string _cookieDomain;
private readonly string _cookieName;
internal HttpClient AuthedClient;
internal Common.Http.Client AuthedClient;
/// <summary>
/// Sets up all the login facilites needed for a INeedsLogin downloader based on having the user log
@ -81,7 +81,7 @@ namespace Wabbajack.Lib.Downloaders
return cookies;
}
public async Task<HttpClient> GetAuthedClient()
public async Task<Common.Http.Client> GetAuthedClient()
{
Helpers.Cookie[] cookies;
try

View File

@ -53,7 +53,7 @@ namespace Wabbajack.Lib.Downloaders
private async Task<HTTPDownloader.State> ToHttpState()
{
var initialURL = $"https://drive.google.com/uc?id={Id}&export=download";
var client = new HttpClient();
var client = new Common.Http.Client();
var response = await client.GetAsync(initialURL);
if (!response.IsSuccessStatusCode)
throw new HttpException((int)response.StatusCode, response.ReasonPhrase);

View File

@ -61,7 +61,7 @@ namespace Wabbajack.Lib.Downloaders
public List<string> Headers { get; set; }
[Exclude]
public HttpClient Client { get; set; }
public Common.Http.Client Client { get; set; }
public override object[] PrimaryKey { get => new object[] {Url};}
@ -86,8 +86,8 @@ namespace Wabbajack.Lib.Downloaders
using (var fs = download ? File.Open(destination, FileMode.Create) : null)
{
var client = Client ?? new HttpClient();
client.DefaultRequestHeaders.Add("User-Agent", Consts.UserAgent);
var client = Client ?? new Common.Http.Client();
client.Headers.Add(("User-Agent", Consts.UserAgent));
if (Headers != null)
foreach (var header in Headers)
@ -95,7 +95,7 @@ namespace Wabbajack.Lib.Downloaders
var idx = header.IndexOf(':');
var k = header.Substring(0, idx);
var v = header.Substring(idx + 1);
client.DefaultRequestHeaders.Add(k, v);
client.Headers.Add((k, v));
}
long totalRead = 0;

View File

@ -53,7 +53,7 @@ namespace Wabbajack.Lib.Downloaders
if (newURL == null || !newURL.StartsWith("http")) return null;
return new HTTPDownloader.State()
{
Client = new HttpClient(),
Client = new Common.Http.Client(),
Url = newURL
};
}

View File

@ -121,7 +121,7 @@ namespace Wabbajack.Lib.FileUploader
public static HttpClient GetAuthorizedClient()
{
var handler = new HttpClientHandler {MaxConnectionsPerServer = MAX_CONNECTIONS};
var handler = new HttpClientHandler {MaxConnectionsPerServer = Consts.MaxConnectionsPerServer};
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("X-API-KEY", AuthorAPI.GetAPIKey());
return client;
@ -144,7 +144,7 @@ namespace Wabbajack.Lib.FileUploader
return await RunJob("UpdateModLists");
}
public static async Task UploadPackagedInis(IEnumerable<IndexedArchive> archives)
public static async Task UploadPackagedInis(WorkQueue queue, IEnumerable<Archive> archives)
{
archives = archives.ToArray(); // defensive copy
Utils.Log($"Packaging {archives.Count()} inis");
@ -153,12 +153,12 @@ namespace Wabbajack.Lib.FileUploader
await using var ms = new MemoryStream();
using (var z = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var archive in archives)
foreach (var e in archives)
{
var state = (AbstractDownloadState)(await DownloadDispatcher.ResolveArchive(archive.IniData));
var entry = z.CreateEntry(Path.GetFileName(archive.Name));
if (e.State == null) continue;
var entry = z.CreateEntry(Path.GetFileName(e.Name));
await using var os = entry.Open();
await os.WriteAsync(Encoding.UTF8.GetBytes(string.Join("\n", state.GetMetaIni())));
await os.WriteAsync(Encoding.UTF8.GetBytes(string.Join("\n", e.State.GetMetaIni())));
}
}

View File

@ -16,12 +16,11 @@ namespace Wabbajack.Lib.LibCefHelpers
{
public static class Helpers
{
public static HttpClient GetClient(IEnumerable<Cookie> cookies, string referer)
public static Common.Http.Client GetClient(IEnumerable<Cookie> cookies, string referer)
{
var container = ToCookieContainer(cookies);
var handler = new HttpClientHandler { CookieContainer = container };
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Referrer = new Uri(referer);
var client = new Common.Http.Client();
client.Headers.Add(("Referrer", referer));
client.Cookies.AddRange(cookies.Select(cookie => new System.Net.Cookie(cookie.Name, cookie.Value, cookie.Path, cookie.Domain)));
return client;
}

View File

@ -129,6 +129,7 @@ namespace Wabbajack.Lib
if (Directory.Exists(ModListOutputFolder))
Utils.DeleteDirectory(ModListOutputFolder);
/*
if (cancel.IsCancellationRequested) return false;
UpdateTracker.NextStep("Inferring metas for game file downloads");
await InferMetas();
@ -137,10 +138,12 @@ namespace Wabbajack.Lib
UpdateTracker.NextStep("Reindexing downloads after meta inferring");
await VFS.AddRoot(MO2DownloadsFolder);
await VFS.WriteToFile(_vfsCacheName);
*/
if (cancel.IsCancellationRequested) return false;
UpdateTracker.NextStep("Pre-validating Archives");
IndexedArchives = Directory.EnumerateFiles(MO2DownloadsFolder)
.Where(f => File.Exists(f + Consts.MetaFileExtension))
.Select(f => new IndexedArchive
@ -152,8 +155,7 @@ namespace Wabbajack.Lib
})
.ToList();
// Don't await this because we don't care if it fails.
var _ = AuthorAPI.UploadPackagedInis(IndexedArchives);
await CleanInvalidArchives();
@ -261,6 +263,11 @@ namespace Wabbajack.Lib
UpdateTracker.NextStep("Gathering Archives");
await GatherArchives();
// Don't await this because we don't care if it fails.
Utils.Log("Finding States to package");
await AuthorAPI.UploadPackagedInis(Queue, SelectedArchives.ToArray());
UpdateTracker.NextStep("Including Archive Metadata");
await IncludeArchiveMetadata();
UpdateTracker.NextStep("Building Patches");
@ -349,7 +356,15 @@ namespace Wabbajack.Lib
await client.GetAsync(
$"http://build.wabbajack.org/indexed_files/{vf.Hash.FromBase64().ToHex()}/meta.ini");
if (!response.IsSuccessStatusCode) return;
if (!response.IsSuccessStatusCode)
{
File.WriteAllLines(vf.FullPath + Consts.MetaFileExtension, new []
{
"[General]",
"unknownArchive=true"
});
return;
}
var ini_data = await response.Content.ReadAsStringAsync();
Utils.Log($"Inferred .meta for {Path.GetFileName(vf.FullPath)}, writing to disk");

View File

@ -24,9 +24,7 @@ namespace Wabbajack.Lib.NexusApi
private static readonly string API_KEY_CACHE_FILE = "nexus.key_cache";
private static string _additionalEntropy = "vtP2HF6ezg";
private static object _diskLock = new object();
public HttpClient HttpClient { get; } = new HttpClient();
public Common.Http.Client HttpClient { get; } = new Common.Http.Client();
#region Authentication
@ -202,14 +200,13 @@ namespace Wabbajack.Lib.NexusApi
{
ApiKey = apiKey;
HttpClient.BaseAddress = new Uri("https://api.nexusmods.com");
// set default headers for all requests to the Nexus API
var headers = HttpClient.DefaultRequestHeaders;
headers.Add("User-Agent", Consts.UserAgent);
headers.Add("apikey", ApiKey);
headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
headers.Add("Application-Name", Consts.AppName);
headers.Add("Application-Version", $"{Assembly.GetEntryAssembly()?.GetName()?.Version ?? new Version(0, 1)}");
var headers = HttpClient.Headers;
headers.Add(("User-Agent", Consts.UserAgent));
headers.Add(("apikey", ApiKey));
headers.Add(("Accept", "application/json"));
headers.Add(("Application-Name", Consts.AppName));
headers.Add(("Application-Version", $"{Assembly.GetEntryAssembly()?.GetName()?.Version ?? new Version(0, 1)}"));
if (!Directory.Exists(Consts.NexusCacheDirectory))
Directory.CreateDirectory(Consts.NexusCacheDirectory);
@ -227,16 +224,17 @@ namespace Wabbajack.Lib.NexusApi
TOP:
try
{
var response = await HttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var response = await HttpClient.GetAsync(url, HttpCompletionOption.ResponseContentRead);
UpdateRemaining(response);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException($"{response.StatusCode} - {response.ReasonPhrase}");
using (var stream = await response.Content.ReadAsStreamAsync())
{
return stream.FromJSON<T>();
Utils.Log($"Nexus call failed: {response.RequestMessage}");
throw new HttpRequestException($"{response.StatusCode} - {response.ReasonPhrase}");
}
await using var stream = await response.Content.ReadAsStreamAsync();
return stream.FromJSON<T>();
}
catch (TimeoutException)
{
@ -246,13 +244,18 @@ namespace Wabbajack.Lib.NexusApi
retries++;
goto TOP;
}
catch (Exception e)
{
Utils.Log(e.ToString());
throw;
}
}
private async Task<T> GetCached<T>(string url)
{
try
{
var builder = new UriBuilder(url) { Host = Consts.WabbajackCacheHostname, Port = Consts.WabbajackCachePort, Scheme = "http" };
var builder = new UriBuilder(url) { Host = Consts.WabbajackCacheHostname, Scheme = "https" };
return await Get<T>(builder.ToString());
}
catch (Exception)
@ -273,6 +276,10 @@ namespace Wabbajack.Lib.NexusApi
}
catch (HttpRequestException)
{
if (await IsPremium())
{
throw;
}
}
try
@ -319,19 +326,6 @@ namespace Wabbajack.Lib.NexusApi
return await GetCached<ModInfo>(url);
}
public async Task<EndorsementResponse> EndorseMod(NexusDownloader.State mod)
{
Utils.Status($"Endorsing ${mod.GameName} - ${mod.ModID}");
var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(mod.GameName)}/mods/{mod.ModID}/endorse.json";
var content = new FormUrlEncodedContent(new Dictionary<string, string> { { "version", mod.Version } });
using (var stream = await HttpClient.PostStream(url, content))
{
return stream.FromJSON<EndorsementResponse>();
}
}
private class DownloadLink
{
public string URI { get; set; }

View File

@ -12,8 +12,8 @@ namespace Wabbajack.Lib
public override string ShortDescription { get; }
public override string ExtendedDescription { get; }
private TaskCompletionSource<(Uri, HttpClient)> _tcs = new TaskCompletionSource<(Uri, HttpClient)>();
public Task<(Uri, HttpClient)> Task => _tcs.Task;
private TaskCompletionSource<(Uri, Common.Http.Client)> _tcs = new TaskCompletionSource<(Uri, Common.Http.Client)>();
public Task<(Uri, Common.Http.Client)> Task => _tcs.Task;
private ManuallyDownloadFile(ManualDownloader.State state)
{
@ -30,7 +30,7 @@ namespace Wabbajack.Lib
_tcs.SetCanceled();
}
public void Resume(Uri s, HttpClient client)
public void Resume(Uri s, Common.Http.Client client)
{
_tcs.SetResult((s, client));
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
</PropertyGroup>

View File

@ -24,7 +24,7 @@ namespace Wabbajack.VirtualFileSystem
Utils.Log("Cleaning VFS, this may take a bit of time");
Utils.DeleteDirectory(_stagingFolder);
}
public const ulong FileVersion = 0x02;
public const ulong FileVersion = 0x03;
public const string Magic = "WABBAJACK VFS FILE";
private static readonly string _stagingFolder = "vfs_staging";
@ -420,12 +420,15 @@ namespace Wabbajack.VirtualFileSystem
public TemporaryDirectory(string name)
{
FullName = name;
if (!Directory.Exists(FullName))
Directory.CreateDirectory(FullName);
}
public string FullName { get; }
public void Dispose()
{
if (Directory.Exists(FullName))
Utils.DeleteDirectory(FullName);
}
}

View File

@ -114,6 +114,20 @@ namespace Wabbajack.VirtualFileSystem
}
public T ThisAndAllChildrenReduced<T>(T acc, Func<T, VirtualFile, T> fn)
{
acc = fn(acc, this);
return this.Children.Aggregate(acc, (current, itm) => itm.ThisAndAllChildrenReduced<T>(current, fn));
}
public void ThisAndAllChildrenReduced(Action<VirtualFile> fn)
{
fn(this);
foreach (var itm in Children)
itm.ThisAndAllChildrenReduced(fn);
}
/// <summary>
/// Returns all the virtual files in the path to this file, starting from the root file.
/// </summary>
@ -233,6 +247,7 @@ namespace Wabbajack.VirtualFileSystem
private void Write(BinaryWriter bw)
{
bw.Write(Name);
bw.Write(FullPath);
bw.Write(Hash);
bw.Write(Size);
bw.Write(LastModified);
@ -258,6 +273,7 @@ namespace Wabbajack.VirtualFileSystem
Context = context,
Parent = parent,
Name = br.ReadString(),
_fullPath = br.ReadString(),
Hash = br.ReadString(),
Size = br.ReadInt64(),
LastModified = br.ReadInt64(),

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
</PropertyGroup>

View File

@ -76,12 +76,20 @@ Global
{5D6A2EAF-6604-4C51-8AE2-A746B4BC5E3E}.Release|Any CPU.ActiveCfg = Release|x64
{5D6A2EAF-6604-4C51-8AE2-A746B4BC5E3E}.Release|x64.ActiveCfg = Release|x64
{5D6A2EAF-6604-4C51-8AE2-A746B4BC5E3E}.Release|x64.Build.0 = Release|x64
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Debug|Any CPU.ActiveCfg = Debug|x64
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Debug|x64.ActiveCfg = Debug|x64
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Debug|x64.Build.0 = Debug|x64
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Release|Any CPU.ActiveCfg = Release|x64
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Release|x64.ActiveCfg = Release|x64
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Release|x64.Build.0 = Release|x64
{37E4D421-8FD3-4D57-8F3A-7A511D6ED5C5}.Debug|Any CPU.ActiveCfg = Debug|x64
{37E4D421-8FD3-4D57-8F3A-7A511D6ED5C5}.Debug|x64.ActiveCfg = Debug|x64
{37E4D421-8FD3-4D57-8F3A-7A511D6ED5C5}.Debug|x64.Build.0 = Debug|x64
{37E4D421-8FD3-4D57-8F3A-7A511D6ED5C5}.Release|Any CPU.ActiveCfg = Release|x64
{37E4D421-8FD3-4D57-8F3A-7A511D6ED5C5}.Release|x64.ActiveCfg = Release|x64
{37E4D421-8FD3-4D57-8F3A-7A511D6ED5C5}.Release|x64.Build.0 = Release|x64
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Debug|x64.ActiveCfg = Debug|Any CPU
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Debug|x64.Build.0 = Debug|Any CPU
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Release|Any CPU.Build.0 = Release|Any CPU
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Release|x64.ActiveCfg = Release|Any CPU
{DE18D89E-39C5-48FD-8E42-16235E3C4593}.Release|x64.Build.0 = Release|Any CPU
{6ED08CFB-B879-4B55-8741-663A4A3491CE}.Debug|Any CPU.ActiveCfg = Debug|x64
{6ED08CFB-B879-4B55-8741-663A4A3491CE}.Debug|x64.ActiveCfg = Debug|x64
{6ED08CFB-B879-4B55-8741-663A4A3491CE}.Debug|x64.Build.0 = Debug|x64