Implemented UI

Added detection of sim running and paused states
Added rudimentary live KML option (needs more styling)
This commit is contained in:
Mark van Renswoude 2020-09-09 21:20:42 +02:00
parent a3cb0eb7ca
commit d6fbff68af
39 changed files with 2325 additions and 563 deletions

View File

@ -1,6 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup> </startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration> </configuration>

View File

@ -55,16 +55,46 @@
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath> <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Context, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Context.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Context.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Coordination, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Coordination.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Coordination.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Interop.WaitHandles, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Interop.WaitHandles.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Interop.WaitHandles.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Oop, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Oop.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Oop.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Tasks, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Tasks.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Tasks.dll</HintPath>
</Reference>
<Reference Include="Nito.Cancellation, Version=1.0.5.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Cancellation.1.0.5\lib\netstandard2.0\Nito.Cancellation.dll</HintPath>
</Reference>
<Reference Include="Nito.Collections.Deque, Version=1.0.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Collections.Deque.1.0.4\lib\netstandard2.0\Nito.Collections.Deque.dll</HintPath>
</Reference>
<Reference Include="Nito.Disposables, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Disposables.2.0.0\lib\netstandard2.0\Nito.Disposables.dll</HintPath>
</Reference>
<Reference Include="SimpleInjector, Version=5.0.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> <Reference Include="SimpleInjector, Version=5.0.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<HintPath>..\packages\SimpleInjector.5.0.3\lib\net461\SimpleInjector.dll</HintPath> <HintPath>..\packages\SimpleInjector.5.0.3\lib\net461\SimpleInjector.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Collections.Immutable, Version=1.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <HintPath>..\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <Reference Include="System.Core" />
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath> <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
@ -77,6 +107,8 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Settings.cs" />
<Compile Include="SimConnectLogger.cs" />
<Compile Include="MainForm.cs"> <Compile Include="MainForm.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@ -85,6 +117,11 @@
</Compile> </Compile>
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\i18n.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>i18n.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="MainForm.resx"> <EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon> <DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
@ -97,6 +134,10 @@
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<EmbeddedResource Include="Resources\i18n.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>i18n.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
@ -115,6 +156,10 @@
<Folder Include="SimConnect\" /> <Folder Include="SimConnect\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FlightLoggerLib\FlightLoggerLib.csproj">
<Project>{D85BCC97-F653-4286-98D9-073A33A55857}</Project>
<Name>FlightLoggerLib</Name>
</ProjectReference>
<ProjectReference Include="..\SimConnect\SimConnect.csproj"> <ProjectReference Include="..\SimConnect\SimConnect.csproj">
<Project>{f160bb6a-7620-41e5-a99c-948c208875e4}</Project> <Project>{f160bb6a-7620-41e5-a99c-948c208875e4}</Project>
<Name>SimConnect</Name> <Name>SimConnect</Name>

View File

@ -18,30 +18,42 @@
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.OutputGroupbox = new System.Windows.Forms.GroupBox(); this.OutputGroupbox = new System.Windows.Forms.GroupBox();
this.OutputKMLPathBrowseButton = new System.Windows.Forms.Button();
this.OutputKMLPathLabel = new System.Windows.Forms.Label();
this.OutputKMLPathTextbox = new System.Windows.Forms.TextBox();
this.OutputCSVPathBrowseButton = new System.Windows.Forms.Button();
this.OutputCSVPathLabel = new System.Windows.Forms.Label();
this.OutputCSVPathTextbox = new System.Windows.Forms.TextBox();
this.OutputKMLCheckbox = new System.Windows.Forms.CheckBox();
this.OutputCSVCheckbox = new System.Windows.Forms.CheckBox();
this.StatusImageList = new System.Windows.Forms.ImageList(this.components); this.StatusImageList = new System.Windows.Forms.ImageList(this.components);
this.FlightSimulatorStatusIcon = new System.Windows.Forms.PictureBox(); this.FlightSimulatorStatusIcon = new System.Windows.Forms.PictureBox();
this.FlightSimulatorLabel = new System.Windows.Forms.Label(); this.FlightSimulatorLabel = new System.Windows.Forms.Label();
this.FlightSimulatorStatusLabel = new System.Windows.Forms.Label(); this.FlightSimulatorStatusLabel = new System.Windows.Forms.Label();
this.RecordButton = new System.Windows.Forms.Button(); this.RecordButton = new System.Windows.Forms.Button();
this.RecordingStatusIcon = new System.Windows.Forms.PictureBox(); this.RecordingStatusIcon = new System.Windows.Forms.PictureBox();
this.OutputCSVCheckbox = new System.Windows.Forms.CheckBox();
this.OutputKMLCheckbox = new System.Windows.Forms.CheckBox();
this.OutputCSVPathTextbox = new System.Windows.Forms.TextBox();
this.OutputCSVPathLabel = new System.Windows.Forms.Label();
this.OutputCSVPathBrowseButton = new System.Windows.Forms.Button();
this.OutputKMLPathBrowseButton = new System.Windows.Forms.Button();
this.OutputKMLPathLabel = new System.Windows.Forms.Label();
this.OutputKMLPathTextbox = new System.Windows.Forms.TextBox();
this.TriggersGroupbox = new System.Windows.Forms.GroupBox(); this.TriggersGroupbox = new System.Windows.Forms.GroupBox();
this.TriggerWaitForMovementCheckbox = new System.Windows.Forms.CheckBox(); this.TriggerConnectedCheckbox = new System.Windows.Forms.CheckBox();
this.TriggerNewLogStationaryCheckbox = new System.Windows.Forms.CheckBox();
this.TriggerNewLogStationaryTimeEdit = new System.Windows.Forms.NumericUpDown();
this.TriggerNewLogStationaryUnitsLabel = new System.Windows.Forms.Label(); this.TriggerNewLogStationaryUnitsLabel = new System.Windows.Forms.Label();
this.TriggerNewLogStationaryTimeEdit = new System.Windows.Forms.NumericUpDown();
this.TriggerNewLogStationaryCheckbox = new System.Windows.Forms.CheckBox();
this.TriggerWaitForMovementCheckbox = new System.Windows.Forms.CheckBox();
this.FolderBrowserDialog = new System.Windows.Forms.FolderBrowserDialog();
this.KMLLiveGroupbox = new System.Windows.Forms.GroupBox();
this.KMLLiveHelpLabel = new System.Windows.Forms.Label();
this.KMLLiveLinkEdit = new System.Windows.Forms.TextBox();
this.KMLLivePortEdit = new System.Windows.Forms.NumericUpDown();
this.KMLLiveLinkLabel = new System.Windows.Forms.Label();
this.KMLLiveCheckbox = new System.Windows.Forms.CheckBox();
this.TryConnectTimer = new System.Windows.Forms.Timer(this.components);
this.ChangesWhileRecordingLabel = new System.Windows.Forms.Label();
this.OutputGroupbox.SuspendLayout(); this.OutputGroupbox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.FlightSimulatorStatusIcon)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.FlightSimulatorStatusIcon)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.RecordingStatusIcon)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.RecordingStatusIcon)).BeginInit();
this.TriggersGroupbox.SuspendLayout(); this.TriggersGroupbox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.TriggerNewLogStationaryTimeEdit)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.TriggerNewLogStationaryTimeEdit)).BeginInit();
this.KMLLiveGroupbox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.KMLLivePortEdit)).BeginInit();
this.SuspendLayout(); this.SuspendLayout();
// //
// OutputGroupbox // OutputGroupbox
@ -59,10 +71,96 @@
this.OutputGroupbox.Location = new System.Drawing.Point(12, 59); this.OutputGroupbox.Location = new System.Drawing.Point(12, 59);
this.OutputGroupbox.Name = "OutputGroupbox"; this.OutputGroupbox.Name = "OutputGroupbox";
this.OutputGroupbox.Size = new System.Drawing.Size(482, 136); this.OutputGroupbox.Size = new System.Drawing.Size(482, 136);
this.OutputGroupbox.TabIndex = 0; this.OutputGroupbox.TabIndex = 1;
this.OutputGroupbox.TabStop = false; this.OutputGroupbox.TabStop = false;
this.OutputGroupbox.Text = " Output "; this.OutputGroupbox.Text = " Output ";
// //
// OutputKMLPathBrowseButton
//
this.OutputKMLPathBrowseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.OutputKMLPathBrowseButton.Enabled = false;
this.OutputKMLPathBrowseButton.Location = new System.Drawing.Point(448, 102);
this.OutputKMLPathBrowseButton.Name = "OutputKMLPathBrowseButton";
this.OutputKMLPathBrowseButton.Size = new System.Drawing.Size(28, 20);
this.OutputKMLPathBrowseButton.TabIndex = 7;
this.OutputKMLPathBrowseButton.Text = "...";
this.OutputKMLPathBrowseButton.UseVisualStyleBackColor = true;
this.OutputKMLPathBrowseButton.Click += new System.EventHandler(this.OutputKMLPathBrowseButton_Click);
//
// OutputKMLPathLabel
//
this.OutputKMLPathLabel.AutoSize = true;
this.OutputKMLPathLabel.Location = new System.Drawing.Point(26, 105);
this.OutputKMLPathLabel.Name = "OutputKMLPathLabel";
this.OutputKMLPathLabel.Size = new System.Drawing.Size(32, 13);
this.OutputKMLPathLabel.TabIndex = 6;
this.OutputKMLPathLabel.Text = "Path:";
//
// OutputKMLPathTextbox
//
this.OutputKMLPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.OutputKMLPathTextbox.Enabled = false;
this.OutputKMLPathTextbox.Location = new System.Drawing.Point(71, 102);
this.OutputKMLPathTextbox.Name = "OutputKMLPathTextbox";
this.OutputKMLPathTextbox.Size = new System.Drawing.Size(373, 20);
this.OutputKMLPathTextbox.TabIndex = 5;
this.OutputKMLPathTextbox.TextChanged += new System.EventHandler(this.Configuration_Changed);
//
// OutputCSVPathBrowseButton
//
this.OutputCSVPathBrowseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.OutputCSVPathBrowseButton.Enabled = false;
this.OutputCSVPathBrowseButton.Location = new System.Drawing.Point(448, 46);
this.OutputCSVPathBrowseButton.Name = "OutputCSVPathBrowseButton";
this.OutputCSVPathBrowseButton.Size = new System.Drawing.Size(28, 20);
this.OutputCSVPathBrowseButton.TabIndex = 4;
this.OutputCSVPathBrowseButton.Text = "...";
this.OutputCSVPathBrowseButton.UseVisualStyleBackColor = true;
this.OutputCSVPathBrowseButton.Click += new System.EventHandler(this.OutputCSVPathBrowseButton_Click);
//
// OutputCSVPathLabel
//
this.OutputCSVPathLabel.AutoSize = true;
this.OutputCSVPathLabel.Location = new System.Drawing.Point(26, 49);
this.OutputCSVPathLabel.Name = "OutputCSVPathLabel";
this.OutputCSVPathLabel.Size = new System.Drawing.Size(32, 13);
this.OutputCSVPathLabel.TabIndex = 3;
this.OutputCSVPathLabel.Text = "Path:";
//
// OutputCSVPathTextbox
//
this.OutputCSVPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.OutputCSVPathTextbox.Enabled = false;
this.OutputCSVPathTextbox.Location = new System.Drawing.Point(71, 46);
this.OutputCSVPathTextbox.Name = "OutputCSVPathTextbox";
this.OutputCSVPathTextbox.Size = new System.Drawing.Size(373, 20);
this.OutputCSVPathTextbox.TabIndex = 2;
this.OutputCSVPathTextbox.TextChanged += new System.EventHandler(this.Configuration_Changed);
//
// OutputKMLCheckbox
//
this.OutputKMLCheckbox.AutoSize = true;
this.OutputKMLCheckbox.Location = new System.Drawing.Point(10, 79);
this.OutputKMLCheckbox.Name = "OutputKMLCheckbox";
this.OutputKMLCheckbox.Size = new System.Drawing.Size(119, 17);
this.OutputKMLCheckbox.TabIndex = 1;
this.OutputKMLCheckbox.Text = "Google Earth (KML)";
this.OutputKMLCheckbox.UseVisualStyleBackColor = true;
this.OutputKMLCheckbox.CheckedChanged += new System.EventHandler(this.OutputKMLCheckbox_CheckedChanged);
//
// OutputCSVCheckbox
//
this.OutputCSVCheckbox.AutoSize = true;
this.OutputCSVCheckbox.Location = new System.Drawing.Point(10, 23);
this.OutputCSVCheckbox.Name = "OutputCSVCheckbox";
this.OutputCSVCheckbox.Size = new System.Drawing.Size(141, 17);
this.OutputCSVCheckbox.TabIndex = 0;
this.OutputCSVCheckbox.Text = "Comma-separated (CSV)";
this.OutputCSVCheckbox.UseVisualStyleBackColor = true;
this.OutputCSVCheckbox.CheckedChanged += new System.EventHandler(this.OutputCSVCheckbox_CheckedChanged);
//
// StatusImageList // StatusImageList
// //
this.StatusImageList.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("StatusImageList.ImageStream"))); this.StatusImageList.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("StatusImageList.ImageStream")));
@ -106,9 +204,10 @@
this.RecordButton.Location = new System.Drawing.Point(348, 17); this.RecordButton.Location = new System.Drawing.Point(348, 17);
this.RecordButton.Name = "RecordButton"; this.RecordButton.Name = "RecordButton";
this.RecordButton.Size = new System.Drawing.Size(108, 23); this.RecordButton.Size = new System.Drawing.Size(108, 23);
this.RecordButton.TabIndex = 4; this.RecordButton.TabIndex = 0;
this.RecordButton.Text = "&Start recording"; this.RecordButton.Text = "<runtime>";
this.RecordButton.UseVisualStyleBackColor = true; this.RecordButton.UseVisualStyleBackColor = true;
this.RecordButton.Click += new System.EventHandler(this.RecordButton_Click);
// //
// RecordingStatusIcon // RecordingStatusIcon
// //
@ -119,151 +218,180 @@
this.RecordingStatusIcon.TabIndex = 5; this.RecordingStatusIcon.TabIndex = 5;
this.RecordingStatusIcon.TabStop = false; this.RecordingStatusIcon.TabStop = false;
// //
// OutputCSVCheckbox
//
this.OutputCSVCheckbox.AutoSize = true;
this.OutputCSVCheckbox.Location = new System.Drawing.Point(10, 23);
this.OutputCSVCheckbox.Name = "OutputCSVCheckbox";
this.OutputCSVCheckbox.Size = new System.Drawing.Size(141, 17);
this.OutputCSVCheckbox.TabIndex = 0;
this.OutputCSVCheckbox.Text = "Comma-separated (CSV)";
this.OutputCSVCheckbox.UseVisualStyleBackColor = true;
//
// OutputKMLCheckbox
//
this.OutputKMLCheckbox.AutoSize = true;
this.OutputKMLCheckbox.Location = new System.Drawing.Point(10, 79);
this.OutputKMLCheckbox.Name = "OutputKMLCheckbox";
this.OutputKMLCheckbox.Size = new System.Drawing.Size(119, 17);
this.OutputKMLCheckbox.TabIndex = 1;
this.OutputKMLCheckbox.Text = "Google Earth (KML)";
this.OutputKMLCheckbox.UseVisualStyleBackColor = true;
//
// OutputCSVPathTextbox
//
this.OutputCSVPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.OutputCSVPathTextbox.Enabled = false;
this.OutputCSVPathTextbox.Location = new System.Drawing.Point(71, 46);
this.OutputCSVPathTextbox.Name = "OutputCSVPathTextbox";
this.OutputCSVPathTextbox.Size = new System.Drawing.Size(373, 20);
this.OutputCSVPathTextbox.TabIndex = 2;
//
// OutputCSVPathLabel
//
this.OutputCSVPathLabel.AutoSize = true;
this.OutputCSVPathLabel.Location = new System.Drawing.Point(26, 49);
this.OutputCSVPathLabel.Name = "OutputCSVPathLabel";
this.OutputCSVPathLabel.Size = new System.Drawing.Size(32, 13);
this.OutputCSVPathLabel.TabIndex = 3;
this.OutputCSVPathLabel.Text = "Path:";
//
// OutputCSVPathBrowseButton
//
this.OutputCSVPathBrowseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.OutputCSVPathBrowseButton.Enabled = false;
this.OutputCSVPathBrowseButton.Location = new System.Drawing.Point(448, 46);
this.OutputCSVPathBrowseButton.Name = "OutputCSVPathBrowseButton";
this.OutputCSVPathBrowseButton.Size = new System.Drawing.Size(28, 20);
this.OutputCSVPathBrowseButton.TabIndex = 4;
this.OutputCSVPathBrowseButton.Text = "...";
this.OutputCSVPathBrowseButton.UseVisualStyleBackColor = true;
//
// OutputKMLPathBrowseButton
//
this.OutputKMLPathBrowseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.OutputKMLPathBrowseButton.Enabled = false;
this.OutputKMLPathBrowseButton.Location = new System.Drawing.Point(448, 102);
this.OutputKMLPathBrowseButton.Name = "OutputKMLPathBrowseButton";
this.OutputKMLPathBrowseButton.Size = new System.Drawing.Size(28, 20);
this.OutputKMLPathBrowseButton.TabIndex = 7;
this.OutputKMLPathBrowseButton.Text = "...";
this.OutputKMLPathBrowseButton.UseVisualStyleBackColor = true;
//
// OutputKMLPathLabel
//
this.OutputKMLPathLabel.AutoSize = true;
this.OutputKMLPathLabel.Location = new System.Drawing.Point(26, 105);
this.OutputKMLPathLabel.Name = "OutputKMLPathLabel";
this.OutputKMLPathLabel.Size = new System.Drawing.Size(32, 13);
this.OutputKMLPathLabel.TabIndex = 6;
this.OutputKMLPathLabel.Text = "Path:";
//
// OutputKMLPathTextbox
//
this.OutputKMLPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.OutputKMLPathTextbox.Enabled = false;
this.OutputKMLPathTextbox.Location = new System.Drawing.Point(71, 102);
this.OutputKMLPathTextbox.Name = "OutputKMLPathTextbox";
this.OutputKMLPathTextbox.Size = new System.Drawing.Size(373, 20);
this.OutputKMLPathTextbox.TabIndex = 5;
//
// TriggersGroupbox // TriggersGroupbox
// //
this.TriggersGroupbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.TriggersGroupbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.TriggersGroupbox.Controls.Add(this.TriggerConnectedCheckbox);
this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryUnitsLabel); this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryUnitsLabel);
this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryTimeEdit); this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryTimeEdit);
this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryCheckbox); this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryCheckbox);
this.TriggersGroupbox.Controls.Add(this.TriggerWaitForMovementCheckbox); this.TriggersGroupbox.Controls.Add(this.TriggerWaitForMovementCheckbox);
this.TriggersGroupbox.Location = new System.Drawing.Point(12, 201); this.TriggersGroupbox.Location = new System.Drawing.Point(12, 201);
this.TriggersGroupbox.Name = "TriggersGroupbox"; this.TriggersGroupbox.Name = "TriggersGroupbox";
this.TriggersGroupbox.Size = new System.Drawing.Size(482, 99); this.TriggersGroupbox.Size = new System.Drawing.Size(482, 124);
this.TriggersGroupbox.TabIndex = 6; this.TriggersGroupbox.TabIndex = 2;
this.TriggersGroupbox.TabStop = false; this.TriggersGroupbox.TabStop = false;
this.TriggersGroupbox.Text = " Triggers "; this.TriggersGroupbox.Text = " Triggers ";
// //
// TriggerConnectedCheckbox
//
this.TriggerConnectedCheckbox.AutoSize = true;
this.TriggerConnectedCheckbox.Location = new System.Drawing.Point(10, 23);
this.TriggerConnectedCheckbox.Name = "TriggerConnectedCheckbox";
this.TriggerConnectedCheckbox.Size = new System.Drawing.Size(324, 17);
this.TriggerConnectedCheckbox.TabIndex = 0;
this.TriggerConnectedCheckbox.Text = " Start recording immediately when connected to Flight Simulator";
this.TriggerConnectedCheckbox.UseVisualStyleBackColor = true;
//
// TriggerNewLogStationaryUnitsLabel
//
this.TriggerNewLogStationaryUnitsLabel.AutoSize = true;
this.TriggerNewLogStationaryUnitsLabel.Location = new System.Drawing.Point(117, 94);
this.TriggerNewLogStationaryUnitsLabel.Name = "TriggerNewLogStationaryUnitsLabel";
this.TriggerNewLogStationaryUnitsLabel.Size = new System.Drawing.Size(47, 13);
this.TriggerNewLogStationaryUnitsLabel.TabIndex = 3;
this.TriggerNewLogStationaryUnitsLabel.Text = "seconds";
//
// TriggerNewLogStationaryTimeEdit
//
this.TriggerNewLogStationaryTimeEdit.Enabled = false;
this.TriggerNewLogStationaryTimeEdit.Location = new System.Drawing.Point(33, 92);
this.TriggerNewLogStationaryTimeEdit.Name = "TriggerNewLogStationaryTimeEdit";
this.TriggerNewLogStationaryTimeEdit.Size = new System.Drawing.Size(78, 20);
this.TriggerNewLogStationaryTimeEdit.TabIndex = 3;
this.TriggerNewLogStationaryTimeEdit.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.TriggerNewLogStationaryTimeEdit.Value = new decimal(new int[] {
30,
0,
0,
0});
this.TriggerNewLogStationaryTimeEdit.ValueChanged += new System.EventHandler(this.Configuration_Changed);
//
// TriggerNewLogStationaryCheckbox
//
this.TriggerNewLogStationaryCheckbox.AutoSize = true;
this.TriggerNewLogStationaryCheckbox.Location = new System.Drawing.Point(10, 69);
this.TriggerNewLogStationaryCheckbox.Name = "TriggerNewLogStationaryCheckbox";
this.TriggerNewLogStationaryCheckbox.Size = new System.Drawing.Size(353, 17);
this.TriggerNewLogStationaryCheckbox.TabIndex = 2;
this.TriggerNewLogStationaryCheckbox.Text = " Start a new log when stationary for at least (excluding when paused):";
this.TriggerNewLogStationaryCheckbox.UseVisualStyleBackColor = true;
this.TriggerNewLogStationaryCheckbox.CheckedChanged += new System.EventHandler(this.TriggerNewLogStationaryCheckbox_CheckedChanged);
//
// TriggerWaitForMovementCheckbox // TriggerWaitForMovementCheckbox
// //
this.TriggerWaitForMovementCheckbox.AutoSize = true; this.TriggerWaitForMovementCheckbox.AutoSize = true;
this.TriggerWaitForMovementCheckbox.Checked = true; this.TriggerWaitForMovementCheckbox.Checked = true;
this.TriggerWaitForMovementCheckbox.CheckState = System.Windows.Forms.CheckState.Checked; this.TriggerWaitForMovementCheckbox.CheckState = System.Windows.Forms.CheckState.Checked;
this.TriggerWaitForMovementCheckbox.Location = new System.Drawing.Point(10, 23); this.TriggerWaitForMovementCheckbox.Location = new System.Drawing.Point(10, 46);
this.TriggerWaitForMovementCheckbox.Name = "TriggerWaitForMovementCheckbox"; this.TriggerWaitForMovementCheckbox.Name = "TriggerWaitForMovementCheckbox";
this.TriggerWaitForMovementCheckbox.Size = new System.Drawing.Size(454, 17); this.TriggerWaitForMovementCheckbox.Size = new System.Drawing.Size(454, 17);
this.TriggerWaitForMovementCheckbox.TabIndex = 0; this.TriggerWaitForMovementCheckbox.TabIndex = 1;
this.TriggerWaitForMovementCheckbox.Text = " Wait for movement before logging the starting point (recommended, ignores initia" + this.TriggerWaitForMovementCheckbox.Text = " Wait for movement before logging the starting point (recommended, ignores initia" +
"l teleports)"; "l teleports)";
this.TriggerWaitForMovementCheckbox.UseVisualStyleBackColor = true; this.TriggerWaitForMovementCheckbox.UseVisualStyleBackColor = true;
this.TriggerWaitForMovementCheckbox.CheckedChanged += new System.EventHandler(this.Configuration_Changed);
// //
// TriggerNewLogStationaryCheckbox // KMLLiveGroupbox
// //
this.TriggerNewLogStationaryCheckbox.AutoSize = true; this.KMLLiveGroupbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
this.TriggerNewLogStationaryCheckbox.Location = new System.Drawing.Point(10, 46); | System.Windows.Forms.AnchorStyles.Right)));
this.TriggerNewLogStationaryCheckbox.Name = "TriggerNewLogStationaryCheckbox"; this.KMLLiveGroupbox.Controls.Add(this.KMLLiveHelpLabel);
this.TriggerNewLogStationaryCheckbox.Size = new System.Drawing.Size(353, 17); this.KMLLiveGroupbox.Controls.Add(this.KMLLiveLinkEdit);
this.TriggerNewLogStationaryCheckbox.TabIndex = 1; this.KMLLiveGroupbox.Controls.Add(this.KMLLivePortEdit);
this.TriggerNewLogStationaryCheckbox.Text = " Start a new log when stationary for at least (excluding when paused):"; this.KMLLiveGroupbox.Controls.Add(this.KMLLiveLinkLabel);
this.TriggerNewLogStationaryCheckbox.UseVisualStyleBackColor = true; this.KMLLiveGroupbox.Controls.Add(this.KMLLiveCheckbox);
this.KMLLiveGroupbox.Location = new System.Drawing.Point(12, 331);
this.KMLLiveGroupbox.Name = "KMLLiveGroupbox";
this.KMLLiveGroupbox.Size = new System.Drawing.Size(482, 97);
this.KMLLiveGroupbox.TabIndex = 3;
this.KMLLiveGroupbox.TabStop = false;
this.KMLLiveGroupbox.Text = " Google Earth live location ";
// //
// TriggerNewLogStationaryTimeEdit // KMLLiveHelpLabel
// //
this.TriggerNewLogStationaryTimeEdit.Enabled = false; this.KMLLiveHelpLabel.AutoSize = true;
this.TriggerNewLogStationaryTimeEdit.Location = new System.Drawing.Point(33, 69); this.KMLLiveHelpLabel.ForeColor = System.Drawing.SystemColors.GrayText;
this.TriggerNewLogStationaryTimeEdit.Name = "TriggerNewLogStationaryTimeEdit"; this.KMLLiveHelpLabel.Location = new System.Drawing.Point(124, 72);
this.TriggerNewLogStationaryTimeEdit.Size = new System.Drawing.Size(78, 20); this.KMLLiveHelpLabel.Name = "KMLLiveHelpLabel";
this.TriggerNewLogStationaryTimeEdit.TabIndex = 2; this.KMLLiveHelpLabel.Size = new System.Drawing.Size(352, 13);
this.TriggerNewLogStationaryTimeEdit.Value = new decimal(new int[] { this.KMLLiveHelpLabel.TabIndex = 5;
30, this.KMLLiveHelpLabel.Text = "Copy this link and use it in the Add > Network Link option in Google Earth";
//
// KMLLiveLinkEdit
//
this.KMLLiveLinkEdit.BackColor = System.Drawing.SystemColors.ButtonFace;
this.KMLLiveLinkEdit.Location = new System.Drawing.Point(123, 49);
this.KMLLiveLinkEdit.Name = "KMLLiveLinkEdit";
this.KMLLiveLinkEdit.ReadOnly = true;
this.KMLLiveLinkEdit.Size = new System.Drawing.Size(353, 20);
this.KMLLiveLinkEdit.TabIndex = 4;
//
// KMLLivePortEdit
//
this.KMLLivePortEdit.Enabled = false;
this.KMLLivePortEdit.Location = new System.Drawing.Point(123, 22);
this.KMLLivePortEdit.Maximum = new decimal(new int[] {
65535,
0, 0,
0, 0,
0}); 0});
this.KMLLivePortEdit.Name = "KMLLivePortEdit";
this.KMLLivePortEdit.Size = new System.Drawing.Size(78, 20);
this.KMLLivePortEdit.TabIndex = 3;
this.KMLLivePortEdit.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.KMLLivePortEdit.Value = new decimal(new int[] {
2020,
0,
0,
0});
this.KMLLivePortEdit.ValueChanged += new System.EventHandler(this.KMLLivePortEdit_ValueChanged);
// //
// TriggerNewLogStationaryUnitsLabel // KMLLiveLinkLabel
// //
this.TriggerNewLogStationaryUnitsLabel.AutoSize = true; this.KMLLiveLinkLabel.AutoSize = true;
this.TriggerNewLogStationaryUnitsLabel.Location = new System.Drawing.Point(117, 71); this.KMLLiveLinkLabel.Location = new System.Drawing.Point(30, 52);
this.TriggerNewLogStationaryUnitsLabel.Name = "TriggerNewLogStationaryUnitsLabel"; this.KMLLiveLinkLabel.Name = "KMLLiveLinkLabel";
this.TriggerNewLogStationaryUnitsLabel.Size = new System.Drawing.Size(47, 13); this.KMLLiveLinkLabel.Size = new System.Drawing.Size(30, 13);
this.TriggerNewLogStationaryUnitsLabel.TabIndex = 3; this.KMLLiveLinkLabel.TabIndex = 1;
this.TriggerNewLogStationaryUnitsLabel.Text = "seconds"; this.KMLLiveLinkLabel.Text = "Link:";
//
// KMLLiveCheckbox
//
this.KMLLiveCheckbox.AutoSize = true;
this.KMLLiveCheckbox.Location = new System.Drawing.Point(10, 23);
this.KMLLiveCheckbox.Name = "KMLLiveCheckbox";
this.KMLLiveCheckbox.Size = new System.Drawing.Size(107, 17);
this.KMLLiveCheckbox.TabIndex = 0;
this.KMLLiveCheckbox.Text = " Enabled on port:";
this.KMLLiveCheckbox.UseVisualStyleBackColor = true;
this.KMLLiveCheckbox.CheckedChanged += new System.EventHandler(this.KMLLiveEnabled_CheckedChanged);
//
// TryConnectTimer
//
this.TryConnectTimer.Interval = 5000;
this.TryConnectTimer.Tick += new System.EventHandler(this.TryConnectTimer_Tick);
//
// ChangesWhileRecordingLabel
//
this.ChangesWhileRecordingLabel.AutoSize = true;
this.ChangesWhileRecordingLabel.ForeColor = System.Drawing.Color.Red;
this.ChangesWhileRecordingLabel.Location = new System.Drawing.Point(191, 43);
this.ChangesWhileRecordingLabel.Name = "ChangesWhileRecordingLabel";
this.ChangesWhileRecordingLabel.Size = new System.Drawing.Size(265, 13);
this.ChangesWhileRecordingLabel.TabIndex = 8;
this.ChangesWhileRecordingLabel.Text = "changes will take effect once the recording is restarted";
this.ChangesWhileRecordingLabel.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.ChangesWhileRecordingLabel.Visible = false;
// //
// MainForm // MainForm
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(506, 309); this.ClientSize = new System.Drawing.Size(506, 440);
this.Controls.Add(this.ChangesWhileRecordingLabel);
this.Controls.Add(this.KMLLiveGroupbox);
this.Controls.Add(this.TriggersGroupbox); this.Controls.Add(this.TriggersGroupbox);
this.Controls.Add(this.RecordingStatusIcon); this.Controls.Add(this.RecordingStatusIcon);
this.Controls.Add(this.RecordButton); this.Controls.Add(this.RecordButton);
@ -271,6 +399,8 @@
this.Controls.Add(this.FlightSimulatorLabel); this.Controls.Add(this.FlightSimulatorLabel);
this.Controls.Add(this.FlightSimulatorStatusIcon); this.Controls.Add(this.FlightSimulatorStatusIcon);
this.Controls.Add(this.OutputGroupbox); this.Controls.Add(this.OutputGroupbox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.Name = "MainForm"; this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "FS Flight Logger"; this.Text = "FS Flight Logger";
@ -281,6 +411,9 @@
this.TriggersGroupbox.ResumeLayout(false); this.TriggersGroupbox.ResumeLayout(false);
this.TriggersGroupbox.PerformLayout(); this.TriggersGroupbox.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.TriggerNewLogStationaryTimeEdit)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.TriggerNewLogStationaryTimeEdit)).EndInit();
this.KMLLiveGroupbox.ResumeLayout(false);
this.KMLLiveGroupbox.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.KMLLivePortEdit)).EndInit();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
@ -308,6 +441,16 @@
private System.Windows.Forms.NumericUpDown TriggerNewLogStationaryTimeEdit; private System.Windows.Forms.NumericUpDown TriggerNewLogStationaryTimeEdit;
private System.Windows.Forms.CheckBox TriggerNewLogStationaryCheckbox; private System.Windows.Forms.CheckBox TriggerNewLogStationaryCheckbox;
private System.Windows.Forms.CheckBox TriggerWaitForMovementCheckbox; private System.Windows.Forms.CheckBox TriggerWaitForMovementCheckbox;
private System.Windows.Forms.FolderBrowserDialog FolderBrowserDialog;
private System.Windows.Forms.GroupBox KMLLiveGroupbox;
private System.Windows.Forms.Label KMLLiveHelpLabel;
private System.Windows.Forms.TextBox KMLLiveLinkEdit;
private System.Windows.Forms.NumericUpDown KMLLivePortEdit;
private System.Windows.Forms.Label KMLLiveLinkLabel;
private System.Windows.Forms.CheckBox KMLLiveCheckbox;
private System.Windows.Forms.Timer TryConnectTimer;
private System.Windows.Forms.Label ChangesWhileRecordingLabel;
private System.Windows.Forms.CheckBox TriggerConnectedCheckbox;
} }
} }

View File

@ -1,12 +1,39 @@
using System.Windows.Forms; using System;
using System.Windows.Forms.VisualStyles; using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using FSFlightLogger.Resources;
using Newtonsoft.Json;
using SimConnect; using SimConnect;
namespace FSFlightLogger namespace FSFlightLogger
{ {
public partial class MainForm : Form public partial class MainForm : Form, ISimConnectClientObserver
{ {
private readonly ISimConnectClientFactory simConnectClientFactory; private readonly ISimConnectClientFactory simConnectClientFactory;
private readonly CancellationTokenSource tryConnectCancellationTokenSource = new CancellationTokenSource();
private ISimConnectClient simConnectClient;
private SimConnectLogger logger;
private enum FlightSimulatorStateValue
{
Connecting,
Connected,
Disconnected,
Failed
}
private enum RecordingStateValue
{
Started,
Stopped
}
public MainForm(ISimConnectClientFactory simConnectClientFactory) public MainForm(ISimConnectClientFactory simConnectClientFactory)
@ -15,9 +42,11 @@ namespace FSFlightLogger
InitializeComponent(); InitializeComponent();
SetFlightSimulatorConnected(false); FlightSimulatorState = FlightSimulatorStateValue.Connecting;
SetRecording(false); RecordingState = RecordingStateValue.Stopped;
//var simConnectClient = simConnectClientFactory.TryConnect("FS Flight Logger"); LoadSettings();
TryConnect();
} }
@ -29,21 +58,333 @@ namespace FSFlightLogger
{ {
if (disposing) if (disposing)
{ {
SaveSettings();
components?.Dispose(); components?.Dispose();
simConnectClient?.DisposeAsync();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
private void SetFlightSimulatorConnected(bool connected) private string GetSettingsFilename()
{ {
FlightSimulatorStatusIcon.Image = StatusImageList.Images[connected ? "FSConnected" : "FSDisconnected"]; return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"FSFlightLogger", @"config.json");
} }
private void SetRecording(bool recording)
private void LoadSettings()
{ {
RecordingStatusIcon.Image = StatusImageList.Images[recording ? "Recording" : "Idle"]; var defaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), i18n.DefaultFolderName);
var settings = new Settings
{
CSVPath = defaultPath,
KMLEnabled = true,
KMLPath = defaultPath,
KMLLivePort = 2020,
TriggerWaitForMovement = true
};
var filename = GetSettingsFilename();
if (File.Exists(filename))
using (var streamReader = new StreamReader(filename, Encoding.UTF8))
{
var serializer = new JsonSerializer();
serializer.Populate(streamReader, settings);
}
if (settings.MainFormLeft.HasValue && settings.MainFormTop.HasValue)
{
StartPosition = FormStartPosition.Manual;
Left = settings.MainFormLeft.Value;
Top = settings.MainFormTop.Value;
// Check if the user did not unplug the screen that last contained the form
var formRectangle = new Rectangle(Left, Top, Width, Height);
if (!Screen.AllScreens.Any(s => s.WorkingArea.IntersectsWith(formRectangle)))
StartPosition = FormStartPosition.CenterScreen;
}
OutputCSVCheckbox.Checked = settings.CSVEnabled;
OutputCSVPathTextbox.Text = settings.CSVPath;
OutputKMLCheckbox.Checked = settings.KMLEnabled;
OutputKMLPathTextbox.Text = settings.KMLPath;
KMLLiveCheckbox.Checked = settings.KMLLiveEnabled;
KMLLivePortEdit.Value = settings.KMLLivePort;
TriggerConnectedCheckbox.Checked = settings.TriggerConnected;
TriggerWaitForMovementCheckbox.Checked = settings.TriggerWaitForMovement;
TriggerNewLogStationaryCheckbox.Checked = settings.TriggerNewLogStationaryEnabled;
TriggerNewLogStationaryTimeEdit.Value = settings.TriggerNewLogStationarySeconds;
UpdateKMLLiveLink();
}
private void SaveSettings()
{
var settings = new Settings
{
MainFormTop = Top,
MainFormLeft = Left,
CSVEnabled = OutputCSVCheckbox.Checked,
CSVPath = OutputCSVPathTextbox.Text,
KMLEnabled = OutputKMLCheckbox.Checked,
KMLPath = OutputKMLPathTextbox.Text,
KMLLiveEnabled = KMLLiveCheckbox.Checked,
KMLLivePort = (int)KMLLivePortEdit.Value,
TriggerConnected = TriggerConnectedCheckbox.Checked,
TriggerWaitForMovement = TriggerWaitForMovementCheckbox.Checked,
TriggerNewLogStationaryEnabled = TriggerNewLogStationaryCheckbox.Checked,
TriggerNewLogStationarySeconds = (int)TriggerNewLogStationaryTimeEdit.Value
};
var filename = GetSettingsFilename();
Directory.CreateDirectory(Path.GetDirectoryName(filename));
using (var streamWriter = new StreamWriter(filename, false, Encoding.UTF8))
{
var serializer = new JsonSerializer();
serializer.Serialize(streamWriter, settings);
}
}
private void TryConnect()
{
FlightSimulatorState = FlightSimulatorStateValue.Connecting;
simConnectClientFactory.TryConnect(@"FS Flight Logger")
.ContinueWith((task, state) =>
{
if (task.Result == null)
{
FlightSimulatorState = FlightSimulatorStateValue.Failed;
TryConnectTimer.Enabled = true;
}
else
{
simConnectClient = task.Result;
simConnectClient.AttachObserver(this);
logger = new SimConnectLogger(simConnectClient);
if (TriggerConnectedCheckbox.Checked)
StartRecording();
FlightSimulatorState = FlightSimulatorStateValue.Connected;
}
},
null, tryConnectCancellationTokenSource.Token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
public void OnQuit()
{
if (InvokeRequired)
{
Invoke(new Action(OnQuit));
return;
}
FlightSimulatorState = FlightSimulatorStateValue.Disconnected;
TryConnectTimer.Enabled = true;
}
private FlightSimulatorStateValue internalFlightSimulatorState;
private FlightSimulatorStateValue FlightSimulatorState
{
get => internalFlightSimulatorState;
set
{
if (value == internalFlightSimulatorState)
return;
internalFlightSimulatorState = value;
var isConnected = value == FlightSimulatorStateValue.Connected;
FlightSimulatorStatusIcon.Image = StatusImageList.Images[isConnected ? @"FSConnected" : @"FSDisconnected"];
switch (value)
{
case FlightSimulatorStateValue.Connecting:
FlightSimulatorStatusLabel.Text = i18n.FlightSimulatorConnecting;
break;
case FlightSimulatorStateValue.Connected:
FlightSimulatorStatusLabel.Text = i18n.FlightSimulatorConnected;
break;
case FlightSimulatorStateValue.Disconnected:
FlightSimulatorStatusLabel.Text = string.Format(i18n.FlightSimulatorQuit, TryConnectTimer.Interval / 1000);
break;
case FlightSimulatorStateValue.Failed:
FlightSimulatorStatusLabel.Text = string.Format(i18n.FlightSimulatorFailed, TryConnectTimer.Interval / 1000);
break;
}
CheckCanRecord();
if (!isConnected)
RecordingState = RecordingStateValue.Stopped;
}
}
private RecordingStateValue internalRecordingState;
private RecordingStateValue RecordingState
{
get => internalRecordingState;
set
{
if (value == internalRecordingState)
return;
internalRecordingState = value;
var isRecording = value == RecordingStateValue.Started;
RecordingStatusIcon.Image = StatusImageList.Images[isRecording ? @"Recording" : @"Idle"];
RecordButton.Text = isRecording ? i18n.RecordButtonStop : i18n.RecordButtonStart;
if (!isRecording)
ChangesWhileRecordingLabel.Visible = false;
}
}
private void StartRecording()
{
SaveSettings();
Task.Run(async () =>
{
await logger.Start(new SimConnectLogger.Config
{
CSVOutputPath = OutputCSVCheckbox.Checked ? OutputCSVPathTextbox.Text : null,
KMLOutputPath = OutputKMLCheckbox.Checked ? OutputKMLPathTextbox.Text : null,
KMLLivePort = KMLLiveCheckbox.Checked ? (int) KMLLivePortEdit.Value : (int?) null,
IntervalTime = TimeSpan.FromSeconds(1), // TODO configurable
IntervalDistance = 1, // TODO configurable
WaitForMovement = TriggerWaitForMovementCheckbox.Checked,
NewLogWhenIdleSeconds = TriggerNewLogStationaryCheckbox.Checked
? TimeSpan.FromSeconds((double) TriggerNewLogStationaryTimeEdit.Value)
: (TimeSpan?) null
});
});
RecordingState = RecordingStateValue.Started;
}
private void StopRecording()
{
Task.Run(async () => await logger.Stop());
RecordingState = RecordingStateValue.Stopped;
}
private void UpdateKMLLiveLink()
{
KMLLiveLinkEdit.Text = $@"http://localhost:{KMLLivePortEdit.Value}/live";
}
private void CheckChangesWhileRecording()
{
if (RecordingState == RecordingStateValue.Started)
ChangesWhileRecordingLabel.Visible = true;
}
private void CheckCanRecord()
{
RecordButton.Enabled = FlightSimulatorState == FlightSimulatorStateValue.Connected &&
(OutputCSVCheckbox.Checked || OutputKMLCheckbox.Checked || KMLLiveCheckbox.Checked);
}
private void TryConnectTimer_Tick(object sender, EventArgs e)
{
TryConnectTimer.Enabled = false;
TryConnect();
}
private void OutputCSVCheckbox_CheckedChanged(object sender, EventArgs e)
{
OutputCSVPathTextbox.Enabled = OutputCSVCheckbox.Checked;
OutputCSVPathBrowseButton.Enabled = OutputCSVCheckbox.Checked;
CheckChangesWhileRecording();
CheckCanRecord();
}
private void OutputKMLCheckbox_CheckedChanged(object sender, EventArgs e)
{
OutputKMLPathTextbox.Enabled = OutputKMLCheckbox.Checked;
OutputKMLPathBrowseButton.Enabled = OutputKMLCheckbox.Checked;
CheckChangesWhileRecording();
CheckCanRecord();
}
private void TriggerNewLogStationaryCheckbox_CheckedChanged(object sender, EventArgs e)
{
TriggerNewLogStationaryTimeEdit.Enabled = TriggerNewLogStationaryCheckbox.Checked;
CheckChangesWhileRecording();
}
private void KMLLiveEnabled_CheckedChanged(object sender, EventArgs e)
{
KMLLivePortEdit.Enabled = KMLLiveCheckbox.Checked;
CheckChangesWhileRecording();
CheckCanRecord();
}
private void SelectPath(Control pathTextBox)
{
FolderBrowserDialog.SelectedPath = pathTextBox.Text;
if (FolderBrowserDialog.ShowDialog(this) == DialogResult.OK)
pathTextBox.Text = FolderBrowserDialog.SelectedPath;
}
private void OutputCSVPathBrowseButton_Click(object sender, EventArgs e)
{
SelectPath(OutputCSVPathTextbox);
}
private void OutputKMLPathBrowseButton_Click(object sender, EventArgs e)
{
SelectPath(OutputKMLPathTextbox);
}
private void KMLLivePortEdit_ValueChanged(object sender, EventArgs e)
{
UpdateKMLLiveLink();
}
private void Configuration_Changed(object sender, EventArgs e)
{
CheckChangesWhileRecording();
}
private void RecordButton_Click(object sender, EventArgs e)
{
if (RecordingState == RecordingStateValue.Started)
StopRecording();
else
StartRecording();
} }
} }
} }

View File

@ -124,351 +124,349 @@
<value> <value>
AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAACO ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAACe
UAAAAk1TRnQBSQFMAgEBBAEAAQgBAAEIAQABIAEAASABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAGA TgAAAk1TRnQBSQFMAgEBBAEAAUwBAAFMAQABIAEAASABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAGA
AwABQAMAAQEBAAEgBgABgP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A AwABQAMAAQEBAAEgBgABgP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A
/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A /wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A
/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8AfgADPAFJA3QBlwOGAcIDiwHZA4sB3AOLAdwDjAHaA4YBwwN0 /wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8AfgADLwFJA08BlwNZAcIDXwHZA2EB3ANhAdwDYgHaA1sBwwNO
AZgDPQFLAwEBAlQAAjwBOwFJAXMBcgFzAZcBfAF7AYgBwgF1AXQBlQHZAXIBcAGYAdwBcgFwAZgB3AF3 AZgDMAFLAwEBAlQAAy8BSQNPAZcDWQHCAl4BYAHZAmEBYgHcAmEBYgHcAmEBYwHaA1sBwwNOAZgDMAFL
AXYBlQHaAXwBewGIAcMCcgFzAZgCPQE8AUsDAQECMAADAQECAwQEBQEHAwUBBwMFAQcDBQEHAwUBBwMF AwEBAjAAAwEBAgMEBAUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEH
AQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMF AwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwMBBAMAAQEQAAMC
AQcDBQEHAwUBBwMFAQcDAwEEAwABARAAAwIBAwMHAQkDCgENAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEO AQMDBwEJAwoBDQMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMK
AwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEO AQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgENAwYBCAMBAQIoAAMMARADTgGW
AwoBDgMKAQ0DBgEIAwEBAigAAw0BEANzAZYDhgHfA4cB/wOEAf8DhAH/A4sB/wOQAf8DkAH/A4sB/wOE A2EB3wOHAf8DhAH/A4QB/wOLAf8DkAH/A5AB/wOLAf8DhAH/A4MB/wOGAf8DZAHkA1ABmwMOARNAAAMM
Af8DgwH/A4YB/wOGAeQDdQGbAw8BE0AAAw0BEAJyAXABlgFoAWcBlQHfAUYBRQG0Af8COQG6Af8BMAEx ARADTgGWAlwBYgHfAT4BPQG0Af8CMQG6Af8BKAEpAcMB/wEpASsB0QH/AS0BLwHYAf8BLQEvAdgB/wEp
AcMB/wExATMB0QH/ATUBNwHYAf8BNQE3AdgB/wExATMB0QH/AjABwgH/AjgBuQH/AUUBRAGzAf8BZwFm ASsB0QH/AigBwgH/AjABuQH/AT0BPAGzAf8CYQFnAeQDUAGbAw4BEycAAQEDBgEIAxABFQMYASIDHgEr
AZYB5AJ0AXEBmwMPARMnAAEBAwYBCAMQARUDGAEiAx4BKwMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMe Ax8BLAMfASwDHwEsAx8BLAMfASwDHwEsAx8BLAMfASwDHwEsAx8BLAMfASwDHwEsAx8BLAMfASwDHwEs
ASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BKwMX Ax8BLAMfASwDHwEsAx8BLAMfASwDHgErAxcBIAMOARMDBAEGDwABAQMLAQ8DHQEpAywBRAM1AVYDNQFX
ASADDgETAwQBBg8AAQEDCwEPAxwBKQErAikBRAEyAi8BVgEyAi8BVwEyAi8BVwEyATABLwFXATMBMAEv AzUBVwM1AVcDNQFXAzUBVwM1AVcDNQFXAzUBVwM1AVcDNQFXAzUBVwM1AVcDNQFXAzUBVwM1AVcDNQFX
AVcBMwEwAS8BVwEzATABLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFX AzUBVwM1AVcDNQFXAzUBVwM1AVYDKgFAAxsBJgMJAQwkAANCAXYDaQHpA3EB/wOGAf8DnQH/A6oB/wO1
ATMCLwFXATMBMAEvAVcBMwEwAS8BVwEzATABLwFXATMBMAEvAVcBMgIvAVcBMgIvAVcBMgEwAS8BVgEp Af8DugH/A70B/wO9Af8DuQH/A7QB/wOqAf8DnQH/A4YB/wNvAf8DawHvA0UBfDgAA0IBdgJiAW4B6QEm
AScBKAFAARsCGgEmAwkBDCQAA14BdgOJAekDeQH/A4YB/wOdAf8DqgH/A7UB/wO6Af8DvQH/A70B/wO5 ASUBsAH/AR8BIAHPAf8BOgE9AecB/wFOAVQB8AH/AV0BaAH3Af8BZgF2AfoB/wFsAYYB+wH/AWsBhgH7
Af8DtAH/A6oB/wOdAf8DhgH/A3cB/wOJAe8DYQF8OAACXwFcAXYCZwGcAekBLgEtAbAB/wEnASgBzwH/ Af8BZQF0AfoB/wFcAWYB9wH/AU0BVAHwAf8BOgE9AecB/wEfASABzwH/AiQBrgH/AmIBcwHvA0UBfCAA
AUIBRQHnAf8BVgFcAfAB/wFlAXAB9wH/AW4BfgH6Af8BdAGGAfsB/wFzAYYB+wH/AW0BfAH6Af8BZAFu AwEBAgMQARUDOQFfA0UBfwNGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGA
AfcB/wFVAVwB8AH/AUIBRQHnAf8BJwEoAc8B/wIsAa4B/wJlAZwB7wJiAV4BfCAAAwEBAgMQARUDNgFf A0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRQF+AzUBVwMLAQ8DAAEB
A0EBfwNCAYADQgGAA0IBgANCAYADQgGAA0MBgANDAYADQwGAA0MBgANDAYADQwGAA0MBgANDAYADQwGA CAADAgEDAx0BKQNWAb4BUAEjARIB/QFWARMBAAH/AVYBEwEAAf8BVwEUAQAB/wFZARQBAAH/AVkBEwEA
A0MBgANDAYADQwGAA0IBgANCAYADQgGAA0IBgANCAYADQQF+AzMBVwMLAQ8DAAEBCAADAgEDAR0CHAEp Af8BWwETAQEB/wFbARMBAQH/AVsBEwEBAf8BWwETAQEB/wFbARMBAQH/AVsBEwEBAf8BWwETAQEB/wFb
AU0BMQErAb4BWwEbAQoB/QFeARsBCAH/AV4BGwEIAf8BXwEcAQgB/wFhARwBCAH/AWEBGwEIAf8BYwEb ARMBAQH/AVsBEwEBAf8BWwETAQEB/wFbARMBAQH/AVsBEwEBAf8BWQEUAQAB/wFZARMBAAH/AVcBEwEA
AQkB/wFjARsBCQH/AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/ Af8BVgETAQAB/wFWARMBAAH/AVsBIQEbAfsDUwGtAxUBHQMBAQIYAAMEAQYDVAGmA20B/wOAAf8DogH/
AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/AWMBGwEJAf8BYQEcAQgB/wFhARsBCAH/AV8BGwEIAf8BXgEb A68B/wO3Af8DvwH/A8YB/wPKAf8DzAH/A8wB/wPKAf8DxQH/A70B/wO2Af8DrwH/A6IB/wOAAf8DagH/
AQgB/wFeARsBCAH/AVsBHAELAfsBSwE0ATABrQMVAR0DAQECGAADBAEGA3gBpgN1Af8DgAH/A6IB/wOv A1YBsQMLAQ8oAAMEAQYDVAGmAikBpAH/AhYBywH/AUEBRAHqAf8BWgFgAe8B/wFjAW8B9gH/AW4BiQH8
Af8DtwH/A78B/wPGAf8DygH/A8wB/wPMAf8DygH/A8UB/wO9Af8DtgH/A68B/wOiAf8DgAH/A3IB/wN9 Af8BgwGbAv8BjQGpAv8BkQGtAv8BkAGsAv8BjAGnAv8BgQGYAv8BbAGGAfsB/wFiAW0B9QH/AVkBXwHv
AbEDDAEPKAADBAEGAnIBeAGmAjEBpAH/Ah4BywH/AUkBTAHqAf8BYgFoAe8B/wFrAXcB9gH/AXYBiQH8 Af8BQQFEAesB/wIXAcwB/wEmASUBoQH/A1YBsQMLAQ8bAAEBAzgBXQNHAYADRgGAA0YBgANGAYADRgGA
Af8BgwGbAv8BjQGpAv8BkQGtAv8BkAGsAv8BjAGnAv8BgQGYAv8BdAGGAfsB/wFqAXUB9QH/AWEBZwHv A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
Af8BSQFMAesB/wIfAcwB/wEuAS0BoQH/AnYBfQGxAwwBDxsAAQEDNwFdA0cBgANFAYADRQGAA0YBgANG A0cBgANGAYADRgGAA0YBgANGAYADRwGAAzEBTgMAAQEIAAMBAQIBVwJVAboBgwEjAQUB/wFuAR0BAwH/
AW8BHgEEAf8BcwEgAQQB/wF2ASABBAH/AYIBIQEFAf8BhAEiAQUB/wGGASIBBQH/AYYBIwEFAf8BhgEj
AQUB/wGGASMBBQH/AYYBIwEFAf8BhgEjAQUB/wGGASMBBQH/AYYBIwEFAf8BhgEjAQUB/wGGASMBBQH/
AYYBIwEFAf8BhQEiAQUB/wGEASIBBQH/AYEBIQEFAf8BdQEhAQQB/wFzAR8BBAH/AW8BHQEEAf8BbgEe
AQMB/wGDASQBBQH/AVACTwGbAwABARQAAxMBGgNgAdcDZgH/A5MB/wOpAf8DsgH/A7kB/wO+Af8DwwH/
A8gB/wPLAf8DygH/A8oB/wPKAf8DxwH/A8EB/wO8Af8DuAH/A7EB/wOoAf8DlAH/A2UB/wNhAd4DFAEc
IAADEwEaAl4BYAHXAQ8BDgGwAf8BLgEvAd0B/wFSAVYB6QH/AV0BZQHxAf8BZgFzAfcB/wFvAYoB+AH/
AYMBmQH6Af8BjQGnAfsB/wGRAa0B/AH/AZABqgH8Af8BkAGrAfwB/wGRAa0B+wH/AYsBpQH6Af8BgAGW
AfgB/wFtAYcB9wH/AWUBcQH2Af8BXAFjAfAB/wFRAVUB6AH/AS8BMAHeAf8BDgENAa8B/wJgAWEB3gMU
ARwUAAMOARMDRgF/A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRQF9
AwQBBggAAxsBJgF/ATQBEAH+AaUBNQEJAf8BqgE3AQoB/wGwATkBCwH/AbYBOwEMAf8BuwE9AQwB/wHA
AUABDQH/AcMBQAENAf8BxAFBAQ0B/wHFAUIBDQH/AcUBQgENAf8BxQFCAQ0B/wHFAUIBDQH/AcUBQgEN
Af8BxQFCAQ0B/wHFAUIBDQH/AcUBQgENAf8BxQFCAQ0B/wHFAUIBDQH/AcQBQQENAf8BwgFAAQ0B/wG/
AUABDQH/AbsBPQEMAf8BtQE7AQwB/wGvATgBCwH/AaoBNwEKAf8BpAE0AQkB/wGAAUEBJAH5AwkBDBQA
A2AB1gNkAf8DmQH/A6cB/wOvAf8DswH/A7cB/wO8Af8DwgH/A8MB/wPEAf8DxQH/A8UB/wPEAf8DwwH/
A8AB/wO7Af8DtgH/A7IB/wOuAf8DpgH/A5kB/wNkAf8DZAHjAwcBCRwAA2AB1gIGAbIB/wE6ATsB3QH/
AVEBVQHmAf8BWgFiAe8B/wFhAW4B7wH/AWkBgwHwAf8BcgGPAfIB/wGEAZsB9QH/AYcBoAH1Af8BiQGj
AfYB/wGKAaUB9gH/AYoBpQH2Af8BiQGjAfUB/wGHAZ8B9QH/AYIBmAHzAf8BcAGNAfEB/wFnAYEB8AH/
AWABbAHvAf8BWQFgAe4B/wFQAVQB5QH/AToBOwHdAf8CBgGyAf8CXwFlAeMDBwEJEAADFwEgA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0YBgANGAYADRQGAA0UBgANHAYADMQFOAwABAQgAAwEBAgFaATcBLgG6AYMBKwENAf8BdgEl AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMOARMIAAMpAT8BogE1
AQsB/wF3ASYBDAH/AXsBKAEMAf8BfgEoAQwB/wGCASkBDQH/AYQBKgENAf8BhgEqAQ0B/wGGASsBDQH/ AQkB/wHDAUQBDQH/Ac0BSAEOAf8B1QFNAREB/wHaAVIBFgH/AdwBVgEbAf8B3QFZASAB/wHqAaIBgAH/
AYYBKwENAf8BhgErAQ0B/wGGASsBDQH/AYYBKwENAf8BhgErAQ0B/wGGASsBDQH/AYYBKwENAf8BhgEr AecBlQFmAf8B3wFfASYB/wHfAV8BJgH/Ad8BXwEmAf8B3wFfASYB/wHfAV8BJgH/Ad8BXwEmAf8B3wFf
AQ0B/wGGASsBDQH/AYUBKgENAf8BhAEqAQ0B/wGBASkBDQH/AX0BKQEMAf8BewEnAQwB/wF3ASUBDAH/ ASYB/wHfAV8BJgH/Ad8BXwEmAf8B3wFfASYB/wHtAbMBlwH/Ad8BYgErAf8B3QFZAR8B/wHbAVYBGgH/
AXYBJgELAf8BgwEsAQ0B/wFRAToBNQGbAwABARQAAxUBGgOIAdcDbgH/A5MB/wOpAf8DsgH/A7kB/wO+ AdkBUQEWAf8B1AFMARAB/wHMAUcBDgH/AcABQwENAf8BmgEvAQgB/wMbASYQAANQAZ0DYQH/A5QB/wOj
Af8DwwH/A8gB/wPLAf8DygH/A8oB/wPKAf8DxwH/A8EB/wO8Af8DuAH/A7EB/wOoAf8DlAH/A20B/wOE Af8DqgH/A6wB/wOxAf8DtQH/A7kB/wO8Af8DvgH/A78B/wO/Af8DvwH/A78B/wO9Af8DuwH/A7kB/wO1
Ad4DFwEcIAADFQEaAnQBjwHXARcBFgGwAf8BNgE3Ad0B/wFaAV4B6QH/AWUBbQHxAf8BbgF7AfcB/wF3 Af8DsAH/A6wB/wOqAf8DowH/A5YB/wNgAf8DVQGyGAADUAGdAgcBqgH/ATYBOAHYAf8BTQFRAeIB/wFU
AYoB+AH/AYMBmQH6Af8BjQGnAfsB/wGRAa0B/AH/AZABqgH8Af8BkAGrAfwB/wGRAa0B+wH/AYsBpQH6 AVwB6QH/AVoBZgHoAf8BYQFyAeoB/wFqAYYB7AH/AXEBkAHtAf8BdgGXAe4B/wGCAZwB7wH/AYMBngHw
Af8BgAGWAfgB/wF1AYcB9wH/AW0BeQH2Af8BZAFrAfAB/wFZAV0B6AH/ATcBOAHeAf8BFgEVAa8B/wJv Af8BhAGfAfAB/wGEAZ4B8AH/AYMBngHwAf8BgQGbAe8B/wF1AZYB7gH/AXABjgHtAf8BaQGEAesB/wFg
AYwB3gMXARwUAAMOARMDSAF/A0wBgANMAYADTQGAA04BgANPAYADUAGAA1ABgANQAYADUAGAA1ABgANQ AXAB6QH/AVkBZAHoAf8BVAFaAegB/wFMAVAB4gH/AjgB2QH/AgcBqQH/A1UBshAAAxcBIANHAYADRwGA
AYADUAGAA1ABgANQAYADUAGAA1ABgANQAYADUAGAA1ABgANQAYADTwGAA08BgANOAYADTQGAA0wBgANM A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
AYADRwF9AwQBBggAAR0CGwEmAY8BNAEQAf4BpQE9AREB/wGqAT8BEgH/AbABQQETAf8BtgFDARQB/wG7 A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AagBNwEK
AUUBFAH/AcABSAEVAf8BwwFIARUB/wHEAUkBFQH/AcUBSgEVAf8BxQFKARUB/wHFAUoBFQH/AcUBSgEV Af8BzgFJAQ4B/wHZAU8BEgH/AdwBVwEcAf8B3gFeASUB/wHfAWMBKgH/AeEBZwEwAf8B+gHrAeQB/wHx
Af8BxQFKARUB/wHFAUoBFQH/AcUBSgEVAf8BxQFKARUB/wHFAUoBFQH/AcUBSgEVAf8BxAFJARUB/wHC AcEBqQH/AeIBawE3Af8B4gFrATcB/wHiAWsBNwH/AeIBawE3Af8B4gFrATcB/wHiAWsBNwH/AeIBawE3
AUgBFQH/Ab8BSAEVAf8BuwFFARQB/wG1AUMBFAH/Aa8BQAETAf8BqgE/ARIB/wGkATwBEQH/AYgBMgET Af8B4gFrATcB/wHiAWsBNwH/AeQBdQFFAf8B/QH8AfsB/wHxAcABqQH/AeEBZgEvAf8B3wFiASkB/wHd
AfkDCQEMFAADhwHWA2wB/wOZAf8DpwH/A68B/wOzAf8DtwH/A7wB/wPCAf8DwwH/A8QB/wPFAf8DxQH/ AV0BIwH/AdwBVgEbAf8B2AFOAREB/wHNAUkBDgH/AZwBMQEIAf8DHAEnDAADNQFWA2QB/wOIAf8DnwH/
A8QB/wPDAf8DwAH/A7sB/wO2Af8DsgH/A64B/wOmAf8DmQH/A2wB/wOFAeMDBwEJHAACcgGPAdYCDgGy A6UB/wOmAf8DqgH/A68B/wOzAf8DtgH/A7gB/wO6Af8DugH/A7sB/wO7Af8DugH/A7kB/wO4Af8DtgH/
Af8BQgFDAd0B/wFZAV0B5gH/AWIBagHvAf8BaQF2Ae8B/wFxAYMB8AH/AXoBjwHyAf8BhAGbAfUB/wGH A7IB/wOuAf8DqQH/A6UB/wOkAf8DngH/A4oB/wNfAf8DOgFiEAADNQFWAhwBnQH/ASYBJwHNAf8BSAFL
AaAB9QH/AYkBowH2Af8BigGlAfYB/wGKAaUB9gH/AYkBowH1Af8BhwGfAfUB/wGCAZgB8wH/AXgBjQHx AdwB/wFPAVQB4wH/AVMBXQHiAf8BWQFnAeQB/wFhAXIB5gH/AWgBhAHoAf8BbQGMAeoB/wFxAZIB7AH/
Af8BbwGBAfAB/wFoAXQB7wH/AWEBaAHuAf8BWAFcAeUB/wFCAUMB3QH/Ag4BsgH/Am0BjgHjAwcBCRAA AXMBlQHtAf8BdAGXAe0B/wF1AZgB7QH/AXUBmAHtAf8BdAGWAe0B/wFyAZUB7QH/AXABkQHsAf8BbAGK
AxgBIANLAYADUAGAA1EBgANTAYADVAGAA1UBgANWAYADYgGAA2ABgANXAYADVwGAA1cBgANXAYADVwGA AeoB/wFnAYMB6AH/AV8BcAHmAf8BWAFmAeMB/wFRAVsB4gH/AU4BUwHjAf8BRwFKAdsB/wEoASkB0AH/
A1cBgANXAYADVwGAA1cBgANXAYADZQGAA1cBgANVAYADVQGAA1QBgANSAYADUQGAA1ABgANKAYADDgET ARgBFwGYAf8DOgFiDAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
CAABLwEqASgBPwGiAT0BEQH/AcMBTAEVAf8BzQFQARYB/wHVAVUBGQH/AdoBWgEeAf8B3AFeASMB/wHd AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AWEBKAH/AeoBogGAAf8B5wGVAW4B/wHfAWcBLgH/Ad8BZwEuAf8B3wFnAS4B/wHfAWcBLgH/Ad8BZwEu AYADRwGAA0cBgAMPARQIAAMpAT8BqwE5AQoB/wHVAUsBDwH/AdsBUwEYAf8B3QFdASMB/wHfAWMBKgH/
Af8B3wFnAS4B/wHfAWcBLgH/Ad8BZwEuAf8B3wFnAS4B/wHfAWcBLgH/Ae0BswGXAf8B3wFqATMB/wHd AeEBaAEyAf8B5wGMAVgB/wP+Af8B8wHEAa4B/wHkAXEBPwH/AeQBcQE/Af8B5AFxAT8B/wHkAXEBPwH/
AWEBJwH/AdsBXgEiAf8B2QFZAR4B/wHUAVQBGAH/AcwBTwEWAf8BwAFLARUB/wGaATcBEAH/AR0BHAEb AeQBcQE/Af8B5AFxAT8B/wHkAXEBPwH/AeQBcQE/Af8B5AFxAT8B/wHvAbQBmAX/Af0B+wH6Af8B4wFu
ASYQAANzAZ0DaQH/A5QB/wOjAf8DqgH/A6wB/wOxAf8DtQH/A7kB/wO8Af8DvgH/A78B/wO/Af8DvwH/ AToB/wHhAWcBMQH/Ad8BYgEpAf8B3QFcASEB/wHbAVMBFgH/AdMBSgEPAf8BngExAQkB/wMcAScLAAEB
A78B/wO9Af8DuwH/A7kB/wO1Af8DsAH/A6wB/wOqAf8DowH/A5YB/wNoAf8DfAGyGAACbQF0AZ0CDwGq A2gB6QNkAf8DmQH/A6AB/wOgAf8DpAH/A6kB/wOtAf8DsQH/A7QB/wO2Af8DuAH/A7kB/wO5Af8DuQH/
Af8BPgFAAdgB/wFVAVkB4gH/AVwBZAHpAf8BYgFuAegB/wFpAXoB6gH/AXIBhgHsAf8BeQGQAe0B/wF+ A7gB/wO3Af8DtgH/A7QB/wOwAf8DrAH/A6gB/wOjAf8DoAH/A58B/wOYAf8DaAH/A28B8QMBAQILAAEB
AZcB7gH/AYIBnAHvAf8BgwGeAfAB/wGEAZ8B8AH/AYQBngHwAf8BgwGeAfAB/wGBAZsB7wH/AX0BlgHu AmEBbgHpAgUBtAH/AUMBRAHVAf8BSQFNAd4B/wFMAVMB3AH/AVIBXAHeAf8BWQFmAeIB/wFfAW8B5QH/
Af8BeAGOAe0B/wFxAYQB6wH/AWgBeAHpAf8BYQFsAegB/wFcAWIB6AH/AVQBWAHiAf8CQAHZAf8CDwGp AWUBgAHoAf8BaQGGAeoB/wFsAYsB7AH/AW4BjQHtAf8BcAGQAe0B/wFxAZEB7gH/AXABkQHuAf8BbwGP
Af8CdAF9AbIQAAMYASADTAGAA1EBgANTAYADVQGAA1YBgANXAYADWAGAA3EBgANoAYADWQGAA1kBgANZ Ae0B/wFuAY0B7AH/AWsBigHrAf8BaAGFAeoB/wFkAXUB5wH/AV4BbgHkAf8BWAFkAeEB/wFRAVsB3QH/
AYADWQGAA1kBgANZAYADWQGAA1kBgANZAYADWwGAA3QBgANoAYADWAGAA1cBgANWAYADVQGAA1MBgANR AUwBUwHbAf8BSAFMAd0B/wFCAUMB1AH/AgkBuAH/AloBewHxAwEBAggAAxcBIANHAYADRwGAA0cBgANH
AYADSgGAAw8BFAgAATABKgEoAT8BqAE/ARIB/wHOAVEBFgH/AdkBVwEaAf8B3AFfASQB/wHeAWYBLQH/ AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
Ad8BawEyAf8B4QFvATgB/wH6AesB5AH/AfEBwQGpAf8B4gFzAT8B/wHiAXMBPwH/AeIBcwE/Af8B4gFz AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/Aa8BOgELAf8B2AFO
AT8B/wHiAXMBPwH/AeIBcwE/Af8B4gFzAT8B/wHiAXMBPwH/AeIBcwE/Af8B5AF9AU0B/wH9AfwB+wH/ AREB/wHcAVgBHQH/Ad8BYAEnAf8B4QFnATAB/wHjAWsBOAH/AfEBvgGmBf8B8wHIAbIB/wHmAXUBRAH/
AfEBwAGpAf8B4QFuATcB/wHfAWoBMQH/Ad0BZQErAf8B3AFeASMB/wHYAVYBGQH/Ac0BUQEWAf8BnAE5 AeYBdgFEAf8B5gF2AUQB/wHmAXYBRAH/AeYBdgFEAf8B5gF2AUQB/wHmAXYBRAH/AeYBdgFEAf8B5gF2
ARAB/wEeARwBGwEnDAADRgFWA2wB/wOIAf8DnwH/A6UB/wOmAf8DqgH/A68B/wOzAf8DtgH/A7gB/wO6 AUQB/wH4AeMB2QX/AfgB4gHYAf8B5AFvAT4B/wHiAWsBNwH/AeEBZgEvAf8B3gFgASYB/wHcAVYBGwH/
Af8DugH/A7sB/wO7Af8DugH/A7kB/wO4Af8DtgH/A7IB/wOuAf8DqQH/A6UB/wOkAf8DngH/A4oB/wNn AdcBTQEQAf8BogEzAQkB/wMcAScIAANCAXMDYAH/A4sB/wOZAf8DnAH/A54B/wOjAf8DpwH/A6sB/wOu
Af8DTgFiEAACRgFFAVYCJAGdAf8BLgEvAc0B/wFQAVMB3AH/AVcBXAHjAf8BWwFlAeIB/wFhAW8B5AH/ Af8DsAH/A7IB/wOzAf8DtQH/A7UB/wO1Af8DtQH/A7MB/wOyAf8DsAH/A60B/wOqAf8DpgH/A6IB/wOd
AWkBegHmAf8BcAGEAegB/wF1AYwB6gH/AXkBkgHsAf8BewGVAe0B/wF8AZcB7QH/AX0BmAHtAf8BfQGY Af8DmwH/A5gB/wOMAf8DXAH/A0gBhggAA0IBcwIQAZ8B/wEvATABywH/AUIBRQHWAf8BRwFMAdcB/wFL
Ae0B/wF8AZYB7QH/AXoBlQHtAf8BeAGRAewB/wF0AYoB6gH/AW8BgwHoAf8BZwF4AeYB/wFgAW4B4wH/ AVMB2AH/AVIBWwHbAf8BVwFjAd8B/wFdAWwB4gH/AWEBcgHkAf8BZQGBAeYB/wFoAYQB5wH/AWkBhwHo
AVkBYwHiAf8BVgFbAeMB/wFPAVIB2wH/ATABMQHQAf8BIAEfAZgB/wJOAUwBYgwAAxgBIANNAYADUgGA Af8BawGKAekB/wFsAYsB6QH/AWwBiwHpAf8BawGKAekB/wFpAYYB5wH/AWcBhAHnAf8BZAGAAeYB/wFh
A1QBgANWAYADVwGAA1gBgANeAYADdAGAA2kBgANaAYADWgGAA1oBgANaAYADWgGAA1oBgANaAYADWgGA AXEB4wH/AVwBagHhAf8BVgFiAd4B/wFRAVoB2gH/AUsBUgHXAf8BRgFLAdYB/wFBAUQB1QH/ATABMQHM
A1oBgANmAYADdQGAA3QBgANaAYADWAGAA1cBgANWAYADVAGAA1IBgANLAYADDwEUCAABMAEqASgBPwGr Af8CDQGbAf8DSAGGCAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AUEBEgH/AdUBUwEXAf8B2wFbASAB/wHdAWUBKwH/Ad8BawEyAf8B4QFwAToB/wHnAYwBYAH/A/4B/wHz AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AcQBrgH/AeQBeQFHAf8B5AF5AUcB/wHkAXkBRwH/AeQBeQFHAf8B5AF5AUcB/wHkAXkBRwH/AeQBeQFH AYADRwGAA0cBgAMPARQIAAMpAT8BsgE8AQsB/wHZAVABEwH/Ad0BWwEgAf8B3wFjASoB/wHmAYoBVQH/
Af8B5AF5AUcB/wHkAXkBRwH/Ae8BtAGYBf8B/QH7AfoB/wHjAXYBQgH/AeEBbwE5Af8B3wFqATEB/wHd Ae8BtgGcAf8B+wHxAewF/wH0AcoBtQH/AecBggFHAf8B5wGCAUcB/wHnAYIBRwH/AecBggFHAf8B5wGC
AWQBKQH/AdsBWwEeAf8B0wFSARcB/wGeATkBEQH/AR8BHAEbAScLAAEBA4YB6QNsAf8DmQH/A6AB/wOg AUcB/wHnAYIBRwH/AecBggFHAf8B5wGCAUcB/wHqAZQBYgH/A/4F/wH0AcsBtwH/AeUBcwFBAf8B4wFu
Af8DpAH/A6kB/wOtAf8DsQH/A7QB/wO2Af8DuAH/A7kB/wO5Af8DuQH/A7gB/wO3Af8DtgH/A7QB/wOw ATwB/wHiAWkBNAH/Ad8BYgEpAf8B3AFaAR8B/wHZAU8BEgH/AaUBNQEJAf8DHAEnCAADYQHcA2EB/wOR
Af8DrAH/A6gB/wOjAf8DoAH/A58B/wOYAf8DcAH/A4MB8QMBAQILAAEBAmEBnAHpAg0BtAH/AUsBTAHV Af8DlgH/A5gB/wObAf8DnwH/A6MB/wOnAf8DqQH/A6sB/wOtAf8DrgH/A68B/wOvAf8DrwH/A68B/wOu
Af8BUQFVAd4B/wFUAVsB3AH/AVoBZAHeAf8BYQFuAeIB/wFnAXcB5QH/AW0BgAHoAf8BcQGGAeoB/wF0 Af8DrQH/A6sB/wOpAf8DpgH/A6MB/wOfAf8DmgH/A5gB/wOWAf8DkQH/A2MB/wNvAfEIAAJcAWIB3AIF
AYsB7AH/AXYBjQHtAf8BeAGQAe0B/wF5AZEB7gH/AXgBkQHuAf8BdwGPAe0B/wF2AY0B7AH/AXMBigHr Aa0B/wE7AT0BzAH/AUABRAHSAf8BRAFJAdEB/wFJAVEB1AH/AU8BWQHXAf8BVAFfAdoB/wFaAWcB3AH/
Af8BcAGFAeoB/wFsAX0B5wH/AWYBdgHkAf8BYAFsAeEB/wFZAWMB3QH/AVQBWwHbAf8BUAFUAd0B/wFK AV0BbQHeAf8BYAFxAeAB/wFiAXUB4QH/AWUBgAHiAf8BZgGDAeMB/wFmAYMB4wH/AWYBgwHjAf8BZgGD
AUsB1AH/AhEBuAH/AloBmAHxAwEBAggAAxgBIANNAYADUwGAA1UBgANXAYADWAGAA1kBgANoAYADdQGA AeIB/wFkAYAB4gH/AWIBdAHhAf8BYAFxAeAB/wFdAWwB3gH/AVkBZQHcAf8BUwFfAdoB/wFOAVcB1wH/
A2kBgANbAYADWwGAA1sBgANbAYADWwGAA1sBgANbAYADWwGAA1sBgANvAYADdQGAA28BgANaAYADWQGA AUgBTwHTAf8BRAFJAdEB/wFAAUQB0gH/ATsBPAHLAf8BCAEHAa8B/wJWAXsB8QgAAxcBIANHAYADRwGA
A1gBgANWAYADVQGAA1MBgANLAYADDwEUCAABMAEqASgBPwGvAUIBEwH/AdgBVgEZAf8B3AFgASUB/wHf A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
AWgBLwH/AeEBbwE4Af8B4wFzAUAB/wHxAb4BpgX/AfMByAGyAf8B5gF9AUwB/wHmAX4BTAH/AeYBfgFM A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AbUBPQEL
Af8B5gF+AUwB/wHmAX4BTAH/AeYBfgFMAf8B5gF+AUwB/wHmAX4BTAH/AeYBfgFMAf8B+AHjAdkF/wH4 Af8B2gFSARUB/wHpAaABdQH/AfcB4QHWAf8B/gL9Df8B+gHtAecB/wHqAZIBXgH/AegBhQFLAf8B6AGF
AeIB2AH/AeQBdwFGAf8B4gFzAT8B/wHhAW4BNwH/Ad4BaAEuAf8B3AFeASMB/wHXAVUBGAH/AaIBOwER AUsB/wHoAYUBSwH/AegBhQFLAf8B6AGFAUsB/wHoAYUBSwH/AegBhQFLAf8B8wHFAa8J/wHvAbEBkwH/
Af8BHwEcARsBJwgAA1oBcwNoAf8DiwH/A5kB/wOcAf8DngH/A6MB/wOnAf8DqwH/A64B/wOwAf8DsgH/ AeYBdQFDAf8B5AFvAT4B/wHiAWoBNgH/AeABZAErAf8B3QFbASEB/wHaAVEBFAH/AacBNQEKAf8DHAEn
A7MB/wO1Af8DtQH/A7UB/wO1Af8DswH/A7IB/wOwAf8DrQH/A6oB/wOmAf8DogH/A50B/wObAf8DmAH/ BAADEAEWA28B/wNxAf8DjwH/A5IB/wOVAf8DmAH/A5sB/wOfAf8DogH/A6UB/wOnAf8DqAH/A6kB/wOq
A4wB/wNkAf8DZQGGCAABWQFYAVoBcwIYAZ8B/wE3ATgBywH/AUoBTQHWAf8BTwFUAdcB/wFTAVsB2AH/ Af8DqgH/A6oB/wOqAf8DqQH/A6gB/wOmAf8DpAH/A6IB/wOfAf8DmwH/A5gB/wOUAf8DkgH/A48B/wN0
AVoBYwHbAf8BXwFrAd8B/wFlAXQB4gH/AWkBegHkAf8BbQGBAeYB/wFwAYQB5wH/AXEBhwHoAf8BcwGK Af8DaQH/AyIBMQMQARYBLQEsAaUB/wEcAR0BugH/ATkBOwHKAf8BPQFBAcwB/wFBAUcBzgH/AUYBTQHQ
AekB/wF0AYsB6QH/AXQBiwHpAf8BcwGKAekB/wFxAYYB5wH/AW8BhAHnAf8BbAGAAeYB/wFpAXkB4wH/ Af8BSgFUAdMB/wFQAVoB1gH/AVQBYAHYAf8BWAFmAdoB/wFbAWoB2wH/AVwBbQHcAf8BXgFwAd0B/wFf
AWQBcgHhAf8BXgFqAd4B/wFZAWIB2gH/AVMBWgHXAf8BTgFTAdYB/wFJAUwB1QH/ATgBOQHMAf8CFQGb AXEB3gH/AV8BcgHeAf8BXwFxAd4B/wFfAXEB3gH/AV4BcAHdAf8BXAFsAdwB/wFaAWoB2wH/AVcBZQHZ
Af8CYgFkAYYIAAMYASADTgGAA1MBgANWAYADVwGAA10BgANmAYADcgGAA3UBgANqAYADXAGAA1wBgANc Af8BUwFfAdgB/wFPAVoB1gH/AUkBUwHTAf8BRQFMAdAB/wFBAUYBzQH/AT0BQQHMAf8BOQE7AckB/wIf
AYADXAGAA1wBgANcAYADXAGAA1wBgANfAYADdAGAA3UBgANqAYADWwGAA1oBgANZAYADVwGAA1UBgANT AbwB/wImAZ4B/wMiATEEAAMXASADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
AYADTAGAAw8BFAgAATABKgEoAT8BsgFEARMB/wHZAVgBGwH/Ad0BYwEoAf8B3wFrATIB/wHmAYoBXQH/ A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
Ae8BtgGcAf8B+wHxAewF/wH0AcoBtQH/AecBggFPAf8B5wGCAU8B/wHnAYIBTwH/AecBggFPAf8B5wGC A0cBgANHAYADRwGAAw8BFAgAAykBPwG3ATwBCwH/AdsBUgEWAf8B7gG1AZoB/wHzAcsBuAH/AfQB0AG/
AU8B/wHnAYIBTwH/AecBggFPAf8B5wGCAU8B/wHqAZQBagH/A/4F/wH0AcsBtwH/AeUBewFJAf8B4wF2 Af8B9gHVAcYB/wH2AdkBywH/Af4B/AH7Bf8B+wHtAecB/wHrAZkBZwH/AegBhwFNAf8B6AGHAU0B/wHo
AUQB/wHiAXEBPAH/Ad8BagExAf8B3AFiAScB/wHZAVcBGgH/AaUBPQERAf8BHwEcARsBJwgAA4AB3ANp AYcBTQH/AegBhwFNAf8B6AGHAU0B/wHoAYcBTQH/AfsB7gHoBf8D/gH/AeoBlQFjAf8B5gF1AUQB/wHk
Af8DkQH/A5YB/wOYAf8DmwH/A58B/wOjAf8DpwH/A6kB/wOrAf8DrQH/A64B/wOvAf8DrwH/A68B/wOv AXEBPwH/AeIBawE3Af8B4AFlASwB/wHdAVwBIwH/AdoBUgEVAf8BqQE1AQoB/wMcAScEAAM4AVwDXgH/
Af8DrgH/A60B/wOrAf8DqQH/A6YB/wOjAf8DnwH/A5oB/wOYAf8DlgH/A5EB/wNrAf8DgQHxCAACXAGU A4MB/wONAf8DjgH/A5EB/wOVAf8DmAH/A5sB/wOeAf8DoAH/A6IB/wOjAf8DpAH/A6UB/wOlAf8DpQH/
AdwCDQGtAf8BQwFFAcwB/wFIAUwB0gH/AUwBUQHRAf8BUQFZAdQB/wFXAWEB1wH/AVwBZwHaAf8BYgFv A6QB/wOkAf8DowH/A6EB/wOgAf8DnQH/A5oB/wOYAf8DlAH/A5EB/wOOAf8DjQH/A4QB/wNZAf8DSQGI
AdwB/wFlAXUB3gH/AWgBeQHgAf8BagF9AeEB/wFtAYAB4gH/AW4BgwHjAf8BbgGDAeMB/wFuAYMB4wH/ AzgBXAIRAZsB/wEqASsBvwH/ATgBOgHHAf8BOgE/AccB/wE+AUQBygH/AUMBSQHNAf8BRwFPAc8B/wFL
AW4BgwHiAf8BbAGAAeIB/wFqAXwB4QH/AWgBeQHgAf8BZQF0Ad4B/wFhAW0B3AH/AVsBZwHaAf8BVgFf AVYB0QH/AVABWwHTAf8BUwFgAdUB/wFVAWQB1gH/AVcBZgHXAf8BWQFoAdgB/wFaAWoB2AH/AVoBawHZ
AdcB/wFQAVcB0wH/AUwBUQHRAf8BSAFMAdIB/wFDAUQBywH/ARABDwGvAf8CVgGYAfEIAAMYASADTgGA Af8BWgFrAdkB/wFZAWkB2AH/AVgBaAHYAf8BVgFmAdcB/wFUAWIB1gH/AVIBXwHVAf8BTwFaAdMB/wFK
A1QBgANiAYADbgGAA3QBgAN1AYADdQGAA3UBgANxAYADXwGAA1wBgANcAYADXAGAA1wBgANcAYADXAGA AVUB0QH/AUYBTgHPAf8BQQFJAc0B/wE9AUIByQH/ATkBPQHHAf8BNwE5AccB/wErASwBvwH/AgwBlgH/
A1wBgANpAYADdQGAA3UBgANlAYADWwGAA1oBgANZAYADVwGAA1YBgANUAYADTAGAAw8BFAgAATABKgEo A0kBiAQAAxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AT8BtQFFARMB/wHaAVoBHQH/AekBoAF9Af8B9wHhAdYB/wH+Av0N/wH6Ae0B5wH/AeoBkgFmAf8B6AGF AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AVMB/wHoAYUBUwH/AegBhQFTAf8B6AGFAVMB/wHoAYUBUwH/AegBhQFTAf8B6AGFAVMB/wHzAcUBrwn/ AYADDwEUCAADKQE/AbgBPAELAf8B2wFSARcB/wHeAV4BJAH/AeABZQEuAf8B4wFrATgB/wHlAXIBQAH/
Ae8BsQGTAf8B5gF9AUsB/wHkAXcBRgH/AeIBcgE+Af8B4AFsATMB/wHdAWMBKQH/AdoBWQEcAf8BpwE9 AeYBdgFEAf8B7gGsAYwB/wL+Af0F/wH8AfIB7gH/AesBnQFtAf8B6AGHAU0B/wHoAYcBTQH/AegBhwFN
ARIB/wEfARwBGwEnBAADEgEWA3cB/wN5Af8DjwH/A5IB/wOVAf8DmAH/A5sB/wOfAf8DogH/A6UB/wOn Af8B6AGHAU0B/wHtAaYBggn/Af0B+AH2Af8B5wGCAUgB/wHmAXYBRAH/AeQBcQE/Af8B4gFrATcB/wHg
Af8DqAH/A6kB/wOqAf8DqgH/A6oB/wOqAf8DqQH/A6gB/wOmAf8DpAH/A6IB/wOfAf8DmwH/A5gB/wOU AWUBLAH/Ad0BXQEjAf8B2gFSARUB/wGqATYBCgH/AxwBJwQAA1EBogNUAf8DhQH/A4oB/wOLAf8DjgH/
Af8DkgH/A48B/wN8Af8DcQH/AygBMQMSARYBNQE0AaUB/wEkASUBugH/AUEBQwHKAf8BRQFJAcwB/wFJ A5EB/wOUAf8DlwH/A5kB/wObAf8DnQH/A54B/wOfAf8DoAH/A6AB/wOgAf8DoAH/A58B/wOeAf8DnQH/
AU8BzgH/AU4BVQHQAf8BUgFcAdMB/wFYAWIB1gH/AVwBaAHYAf8BYAFuAdoB/wFjAXIB2wH/AWQBdQHc A5sB/wOYAf8DlgH/A5MB/wOQAf8DjQH/A4oB/wOJAf8DhQH/A1YB/wNZAb4DUQGiAgABmAH/AS8BMAG9
Af8BZgF4Ad0B/wFnAXkB3gH/AWcBegHeAf8BZwF5Ad4B/wFnAXkB3gH/AWYBeAHdAf8BZAF0AdwB/wFi Af8BNQE3AcMB/wE3ATsBwgH/ATsBQAHFAf8BPwFGAcgB/wFDAUwBygH/AUcBUgHMAf8BSwFWAc0B/wFO
AXIB2wH/AV8BbQHZAf8BWwFnAdgB/wFXAWIB1gH/AVEBWwHTAf8BTQFUAdAB/wFJAU4BzQH/AUUBSQHM AVoBzwH/AVABXgHQAf8BUgFhAdEB/wFTAWIB0QH/AVQBZAHSAf8BVQFlAdMB/wFVAWQB0wH/AVQBZAHS
Af8BQQFDAckB/wInAbwB/wIuAZ4B/wMoATEEAAMYASADTgGAA1QBgANmAYADagGAA2sBgANsAYADbQGA Af8BUwFiAdEB/wFRAWAB0QH/AVABXQHQAf8BTQFZAc8B/wFKAVYBzQH/AUcBUQHLAf8BQgFLAckB/wE+
A3QBgAN1AYADcQGAA2ABgANdAYADXQGAA10BgANdAYADXQGAA10BgANxAYADdQGAA3QBgANgAYADWwGA AUUBxwH/AToBPwHEAf8BNQE5AcIB/wE0ATcBwgH/Ai8BvQH/AQEBAAGbAf8DWQG+BAADFwEgA0cBgANH
A1oBgANZAYADVwGAA1YBgANUAYADTAGAAw8BFAgAATABKgEoAT8BtwFEARMB/wHbAVoBHgH/Ae4BtQGa AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
Af8B8wHLAbgB/wH0AdABvwH/AfYB1QHGAf8B9gHZAcsB/wH+AfwB+wX/AfsB7QHnAf8B6wGZAW8B/wHo AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMPARQIAAMpAT8BuQE9
AYcBVQH/AegBhwFVAf8B6AGHAVUB/wHoAYcBVQH/AegBhwFVAf8B6AGHAVUB/wH7Ae4B6AX/A/4B/wHq AQsB/wHbAVIBFwH/Ad4BXgEkAf8B4AFlAS4B/wHjAWsBOAH/AeUBcgFAAf8B5gF2AUQB/wHnAYIBSAH/
AZUBawH/AeYBfQFMAf8B5AF5AUcB/wHiAXMBPwH/AeABbQE0Af8B3QFkASsB/wHaAVoBHQH/AakBPQES Ae8BsQGTAf8D/gX/AfwB9gH0Af8B7AGkAYAB/wHoAYcBTQH/AegBhwFNAf8B6AGHAU0B/wH1AdEBvwn/
Af8BHwEcARsBJwQAA0kBXANmAf8DgwH/A40B/wOOAf8DkQH/A5UB/wOYAf8DmwH/A54B/wOgAf8DogH/ AfgB4AHUAf8B5wGCAUcB/wHmAXYBRAH/AeQBcQE/Af8B4gFrATcB/wHgAWUBLAH/Ad0BXQEjAf8B2gFS
A6MB/wOkAf8DpQH/A6UB/wOlAf8DpAH/A6QB/wOjAf8DoQH/A6AB/wOdAf8DmgH/A5gB/wOUAf8DkQH/ ARUB/wGrATcBCgH/AxwBJwQAA1wByQNXAf8DggH/A4YB/wOHAf8DigH/A40B/wOPAf8DkQH/A5QB/wOW
A44B/wONAf8DhAH/A2EB/wNnAYgCRwFKAVwCGQGbAf8BMgEzAb8B/wFAAUIBxwH/AUIBRwHHAf8BRgFM Af8DlwH/A5gB/wOaAf8DmgH/A5oB/wOaAf8DmgH/A5oB/wOYAf8DlwH/A5YB/wOTAf8DkQH/A48B/wON
AcoB/wFLAVEBzQH/AU8BVwHPAf8BUwFeAdEB/wFYAWMB0wH/AVsBaAHVAf8BXQFsAdYB/wFfAW4B1wH/ Af8DiQH/A4YB/wOFAf8DggH/A1oB/wNgAdQDXAHJAgABngH/Ai4BuQH/ATEBNAG+Af8BMwE2Ab4B/wE3
AWEBcAHYAf8BYgFyAdgB/wFiAXMB2QH/AWIBcwHZAf8BYQFxAdgB/wFgAXAB2AH/AV4BbgHXAf8BXAFq ATwBwQH/AToBQQHDAf8BPQFGAcUB/wFBAUsBxwH/AUUBUAHJAf8BSAFUAcoB/wFKAVcBywH/AUsBWQHM
AdYB/wFaAWcB1QH/AVcBYgHTAf8BUgFdAdEB/wFOAVYBzwH/AUkBUQHNAf8BRQFKAckB/wFBAUUBxwH/ Af8BTQFbAc0B/wFOAV0BzQH/AU4BXQHNAf8BTgFdAc0B/wFOAV0BzQH/AU0BWwHNAf8BSwFYAcsB/wFK
AT8BQQHHAf8BMwE0Ab8B/wIUAZYB/wJjAWcBiAQAAxgBIANOAYADVAGAA1YBgANYAYADWQGAA1sBgANb AVYBywH/AUcBUwHKAf8BRAFQAcgB/wFBAUoBxgH/AT0BRQHFAf8BOgFAAcMB/wE2ATwBwAH/ATMBNgG9
AYADZAGAA3QBgAN1AYADcgGAA2EBgANdAYADXQGAA10BgANdAYADYwGAA3UBgAN1AYADcwGAA1wBgANb Af8BMQEzAb0B/wItAbkB/wIDAaEB/wJcAWAB1AQAAxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADWgGAA1kBgANXAYADVgGAA1QBgANMAYADDwEUCAABMAEqASgBPwG4AUQBEwH/AdsBWgEfAf8B3gFm AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
ASwB/wHgAW0BNgH/AeMBcwFAAf8B5QF6AUgB/wHmAX4BTAH/Ae4BrAGMAf8C/gH9Bf8B/AHyAe4B/wHr AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AboBPQELAf8B2wFTARcB/wHeAV4BJAH/
AZ0BdQH/AegBhwFVAf8B6AGHAVUB/wHoAYcBVQH/AegBhwFVAf8B7QGmAYIJ/wH9AfgB9gH/AecBggFQ AeABZQEuAf8B4wFrATgB/wHlAXIBQAH/AeYBdgFEAf8B5wGCAUgB/wHoAYYBTAH/AfIBwAGoAf8D/gX/
Af8B5gF+AUwB/wHkAXkBRwH/AeIBcwE/Af8B4AFtATQB/wHdAWUBKwH/AdoBWgEdAf8BqgE+ARIB/wEf Af0B+gH4Af8B7gGqAYgB/wHoAYcBTQH/Ae8BsAGRAf8B/gH9AfwJ/wHzAcIBqgH/AecBggFHAf8B5gF2
ARwBGwEnBAADcwGiA1wB/wOFAf8DigH/A4sB/wOOAf8DkQH/A5QB/wOXAf8DmQH/A5sB/wOdAf8DngH/ AUQB/wHkAXEBPwH/AeIBawE3Af8B4AFlASwB/wHdAV0BIwH/AdoBUgEVAf8BrAE3AQoB/wMcAScEAANZ
A58B/wOgAf8DoAH/A6AB/wOgAf8DnwH/A54B/wOdAf8DmwH/A5gB/wOWAf8DkwH/A5AB/wONAf8DigH/ AccDWgH/A3YB/wOCAf8DgwH/A4UB/wOIAf8DiwH/A40B/wOOAf8DkQH/A5IB/wOTAf8DlAH/A5QB/wOU
A4kB/wOFAf8DXgH/A3sBvgJmAXgBogEHAQYBmAH/ATcBOAG9Af8BPQE/AcMB/wE/AUMBwgH/AUMBSAHF Af8DlAH/A5QB/wOTAf8DkwH/A5IB/wOQAf8DjgH/A4wB/wOKAf8DhwH/A4UB/wODAf8DggH/A3UB/wNb
Af8BRwFOAcgB/wFLAVQBygH/AU8BWgHMAf8BUwFeAc0B/wFWAWIBzwH/AVgBZgHQAf8BWgFpAdEB/wFb Af8DYgHdAlkBWgHHAgMBnwH/AisBtQH/AS4BMAG4Af8BLwEyAbkB/wEyATcBuwH/ATYBOwG+Af8BOQFA
AWoB0QH/AVwBbAHSAf8BXQFtAdMB/wFdAWwB0wH/AVwBbAHSAf8BWwFqAdEB/wFZAWgB0QH/AVgBZQHQ AcAB/wE8AUQBwQH/AT4BSAHDAf8BQQFNAcUB/wFDAU8BxQH/AUUBUQHGAf8BRwFUAccB/wFHAVUBxgH/
Af8BVQFhAc8B/wFSAV4BzQH/AU8BWQHLAf8BSgFTAckB/wFGAU0BxwH/AUIBRwHEAf8BPQFBAcIB/wE8 AUcBVQHHAf8BRwFVAccB/wFHAVUBxgH/AUYBVAHGAf8BRQFRAcYB/wFDAU8BxQH/AUEBTAHEAf8BPwFI
AT8BwgH/AjcBvQH/AQkBCAGbAf8CagGCAb4EAAMYASADTgGAA1QBgANWAYADWAGAA1kBgANbAYADWwGA AcIB/wE7AUQBwQH/ATgBPwG/Af8BNQE7Ab0B/wEyATYBuwH/AS8BMwG5Af8BLgEvAbgB/wIqAbQB/wIF
A1wBgANlAYADdAGAA3UBgANzAYADYgGAA10BgANdAYADXQGAA2sBgAN1AYADdQGAA24BgANcAYADWwGA AaEB/wJYAWQB3QQAAxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A1oBgANZAYADVwGAA1YBgANUAYADTQGAAw8BFAgAATABKgEoAT8BuQFFARMB/wHbAVoBHwH/Ad4BZgEs A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
Af8B4AFtATYB/wHjAXMBQAH/AeUBegFIAf8B5gF+AUwB/wHnAYIBUAH/Ae8BsQGTAf8D/gX/AfwB9gH0 A0cBgANHAYADDwEUCAADKQE/AboBPQELAf8B2wFTARcB/wHeAV4BJAH/AeABZQEuAf8B4wFrATkB/wHl
Af8B7AGkAYAB/wHoAYcBVQH/AegBhwFVAf8B6AGHAVUB/wH1AdEBvwn/AfgB4AHUAf8B5wGCAU8B/wHm AXUBRQH/AeYBhQFOAf8B5wGKAVUB/wHoAY8BWgH/AegBkgFfAf8B9QHWAccB/wP+Bf8B/gH8AfsB/wHz
AX4BTAH/AeQBeQFHAf8B4gFzAT8B/wHgAW0BNAH/Ad0BZQErAf8B2gFaAR0B/wGrAT8BEgH/AR8BHAEb Ac4BvAH/Af4C/A3/Ae8BtAGXAf8B5wGJAVQB/wHmAYUBTQH/AeQBdAFDAf8B4gFrATcB/wHgAWUBLAH/
AScEAAN9AckDXwH/A4IB/wOGAf8DhwH/A4oB/wONAf8DjwH/A5EB/wOUAf8DlgH/A5cB/wOYAf8DmgH/ Ad0BXQEjAf8B2gFSARUB/wGsATcBCgH/AxwBJwQAA1sByANWAf8DcgH/A3UB/wN2Af8DgQH/A4MB/wOE
A5oB/wOaAf8DmgH/A5oB/wOaAf8DmAH/A5cB/wOWAf8DkwH/A5EB/wOPAf8DjQH/A4kB/wOGAf8DhQH/ Af8DhgH/A4gB/wOKAf8DiwH/A40B/wONAf8DjQH/A40B/wONAf8DjQH/A40B/wOMAf8DiwH/A4kB/wOH
A4IB/wNiAf8DfAHUAmIBiwHJAQgBBwGeAf8CNgG5Af8BOQE8Ab4B/wE7AT4BvgH/AT8BRAHBAf8BQgFJ Af8DhQH/A4QB/wODAf8DgQH/A3UB/wN1Af8DcgH/A1gB/wNhAdwCWwFcAcgBAQEAAZsB/wEnASgBsAH/
AcMB/wFFAU4BxQH/AUkBUwHHAf8BTQFYAckB/wFQAVwBygH/AVIBXwHLAf8BUwFhAcwB/wFVAWMBzQH/ ASoBLAGzAf8BLAEvAbQB/wEuATMBtgH/ATEBNgG4Af8BMwE5AbkB/wE1AT0BuwH/ATcBQAG8Af8BOgFE
AVYBZQHNAf8BVgFlAc0B/wFWAWUBzQH/AVYBZQHNAf8BVQFjAc0B/wFTAWABywH/AVIBXgHLAf8BTwFb Ab0B/wE7AUYBvgH/AT4BSgHAAf8BPwFMAcAB/wE/AUwBwAH/AT8BTAHAAf8BPwFMAcAB/wE/AUwBwAH/
AcoB/wFMAVgByAH/AUkBUgHGAf8BRQFNAcUB/wFCAUgBwwH/AT4BRAHAAf8BOwE+Ab0B/wE5ATsBvQH/ AT8BSwHAAf8BPQFJAb8B/wE7AUYBvgH/ATkBQwG9Af8BNwE/AbsB/wE0AT0BugH/ATIBOQG5Af8BMAE2
AjUBuQH/AgsBoQH/Al0BiwHUBAADGAEgA08BgANUAYADVgGAA1gBgANZAYADWwGAA1sBgANcAYADXAGA AbcB/wEuATIBtQH/ASsBLgGzAf8BKgErAbMB/wInAbAB/wEDAQIBnQH/AlwBYQHcBAADFwEgA0cBgANH
A2gBgAN0AYADdQGAA3MBgANkAYADXQGAA2UBgAN0AYADdQGAA3UBgANoAYADXAGAA1sBgANaAYADWQGA AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
A1cBgANWAYADVAGAA00BgAMPARQIAAEwASoBKAE/AboBRQETAf8B2wFbAR8B/wHeAWYBLAH/AeABbQE2 AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMPARQIAAMpAT8BugE9
Af8B4wFzAUAB/wHlAXoBSAH/AeYBfgFMAf8B5wGCAVAB/wHoAYYBVAH/AfIBwAGoAf8D/gX/Af0B+gH4 AQsB/wHbAVQBGAH/Ad8BaQEzAf8B4gGCAUsB/wHkAY0BWgH/AeYBkgFhAf8B5wGUAWUB/wHoAZYBZwH/
Af8B7gGqAYgB/wHoAYcBVQH/Ae8BsAGRAf8B/gH9AfwJ/wHzAcIBqgH/AecBggFPAf8B5gF+AUwB/wHk AegBmQFpAf8B6AGZAWoB/wHpAZsBbQH/AfcB3gHTGf8D/gH/AeoBpAGDAf8B6AGVAWcB/wHnAZQBZAH/
AXkBRwH/AeIBcwE/Af8B4AFtATQB/wHdAWUBKwH/AdoBWgEdAf8BrAE/ARIB/wEfARwBGwEnBAADdgHH AeYBkQFgAf8B5AGNAVkB/wHiAYEBSQH/Ad8BaAEyAf8B2gFTARYB/wGsATcBCgH/AxwBJwQAA1kBwgNP
A2IB/wN+Af8DggH/A4MB/wOFAf8DiAH/A4sB/wONAf8DjgH/A5EB/wOSAf8DkwH/A5QB/wOUAf8DlAH/ Af8DbgH/A3EB/wNyAf8DcgH/A3QB/wOBAf8DhAH/A4YB/wOIAf8DiQH/A4sB/wOMAf8DjAH/A4wB/wOM
A5QB/wOUAf8DkwH/A5MB/wOSAf8DkAH/A44B/wOMAf8DigH/A4cB/wOFAf8DgwH/A4IB/wN9Af8DYwH/ Af8DjAH/A4wB/wOLAf8DiQH/A4gB/wOGAf8DhAH/A4EB/wNzAf8DcgH/A3EB/wNwAf8DbgH/A1IB/wNd
A3oB3QJZAYYBxwILAZ8B/wIzAbUB/wE2ATgBuAH/ATcBOgG5Af8BOgE/AbsB/wE+AUMBvgH/AUEBSAHA AdIDWQHCAgABkgH/AiQBqwH/AScBKAGvAf8BKAErAa8B/wEoASwBrwH/ASoBMAGxAf8BMAE2AbQB/wE0
Af8BRAFMAcEB/wFGAVABwwH/AUkBVQHFAf8BSwFXAcUB/wFNAVkBxgH/AU8BXAHHAf8BTwFdAcYB/wFP ATsBtgH/ATcBPwG4Af8BOgFDAboB/wE8AUUBugH/AT4BSAG8Af8BPwFKAb0B/wE/AUsBvQH/AUABSwG8
AV0BxwH/AU8BXQHHAf8BTwFdAcYB/wFOAVwBxgH/AU0BWQHGAf8BSwFXAcUB/wFJAVQBxAH/AUcBUAHC Af8BQAFLAb0B/wE/AUoBvQH/AT8BSgG9Af8BPgFIAbwB/wE7AUQBugH/AToBQgG5Af8BNwE/AbgB/wE0
Af8BQwFMAcEB/wFAAUcBvwH/AT0BQwG9Af8BOgE+AbsB/wE3ATsBuQH/ATYBNwG4Af8CMgG0Af8CDQGh AToBtgH/ATABNgG0Af8BKgEvAbAB/wEoASsBrwH/AScBKgGuAf8BJgEoAa4B/wIjAasB/wIAAZUB/wJZ
Af8CVgGOAd0EAAMYASADTwGAA1QBgANWAYADWAGAA1kBgANbAYADXAGAA10BgANeAYADXwGAA2wBgAN0 AV0B0gQAAxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADdQGAA3QBgANrAYADdAGAA3UBgAN1AYADdQGAA2YBgANdAYADXAGAA1sBgANZAYADVwGAA1YBgANU AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADTQGAAw8BFAgAATABKgEoAT8BugFFARMB/wHbAVsBHwH/Ad4BZgEsAf8B4AFtATYB/wHjAXMBQQH/ AYADDwEUCAADKQE/AboBPQELAf8B3AFhASsB/wHiAYcBVAH/AeMBjgFbAf8B5QGSAWEB/wHmAZUBZQH/
AeUBfQFNAf8B5gGFAVYB/wHnAYoBXQH/AegBjwFiAf8B6AGSAWcB/wH1AdYBxwH/A/4F/wH+AfwB+wH/ AecBmAFqAf8B6AGaAW0B/wHoAZwBbgH/AegBnAFvAf8B6AGcAW8B/wHoAZ8BcwH/AfoB7QHnFf8B/AH0
AfMBzgG8Af8B/gL8Df8B7wG0AZcB/wHnAYkBXAH/AeYBhQFVAf8B5AF8AUsB/wHiAXMBPwH/AeABbQE0 AfEB/wHoAZsBbwH/AegBmQFsAf8B5wGXAWkB/wHmAZUBZAH/AeUBkgFhAf8B4wGNAVoB/wHhAYcBUwH/
Af8B3QFlASsB/wHaAVoBHQH/AawBPwESAf8BHwEcARsBJwQAA3UByANeAf8DegH/A30B/wN+Af8DgQH/ AdwBYQEqAf8BrAE3AQoB/wMcAScEAANQAZoDSQH/A2oB/wNuAf8DbAH/A3AB/wOCAf8DhwH/A4kB/wOK
A4MB/wOEAf8DhgH/A4gB/wOKAf8DiwH/A40B/wONAf8DjQH/A40B/wONAf8DjQH/A40B/wOMAf8DiwH/ Af8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjgH/A40B/wONAf8DjAH/A4wB/wOKAf8DiQH/A4gB/wOG
A4kB/wOHAf8DhQH/A4QB/wODAf8DgQH/A30B/wN9Af8DegH/A2AB/wN4AdwCWAGGAcgBCQEIAZsB/wEv Af8DgQH/A28B/wNrAf8DbQH/A2kB/wNKAf8DWAG8A1ABmgIAAYcB/wIgAaYB/wEkASUBqgH/ASABIwGp
ATABsAH/ATIBNAGzAf8BNAE3AbQB/wE2ATsBtgH/ATkBPgG4Af8BOwFBAbkB/wE9AUUBuwH/AT8BSAG8 Af8BJgEqAawB/wE1ATkBsgH/ATsBPwG1Af8BPQFCAbcB/wE/AUUBuAH/AUEBRwG5Af8BQgFKAboB/wFD
Af8BQgFMAb0B/wFDAU4BvgH/AUYBUgHAAf8BRwFUAcAB/wFHAVQBwAH/AUcBVAHAAf8BRwFUAcAB/wFH AUsBuwH/AUQBTAG8Af8BRAFMAbwB/wFFAU0BvAH/AUUBTQG8Af8BRAFMAbsB/wFEAUsBuwH/AUMBSgG6
AVQBwAH/AUcBUwHAAf8BRQFRAb8B/wFDAU4BvgH/AUEBSwG9Af8BPwFHAbsB/wE8AUUBugH/AToBQQG5 Af8BQgFJAboB/wFAAUcBuAH/AT4BRQG3Af8BPAFBAbYB/wE6AT4BtQH/ATQBOAGxAf8BJgEpAasB/wEg
Af8BOAE+AbcB/wE2AToBtQH/ATMBNgGzAf8BMgEzAbMB/wIvAbAB/wELAQoBnQH/AlQBjAHcBAADGAEg ASIBqAH/AiQBqQH/ASABHwGlAf8CAAGKAf8DWAG8BAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A08BgANUAYADWAGAA1wBgANeAYADXwGAA18BgANgAYADYAGAA2ABgANhAYADbgGAA3UBgAN1AYADdQGA A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A3UBgAN1AYADdQGAA3QBgANiAYADYAGAA18BgANfAYADXgGAA1sBgANYAYADVAGAA00BgAMPARQIAAEw A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMPARQIAAMpAT8BugE9AQsB/wHdAWUBMwH/AeIBiwFb
ASoBKAE/AboBRQETAf8B2wFcASAB/wHfAXEBOwH/AeIBggFTAf8B5AGNAWIB/wHmAZIBaQH/AecBlAFt Af8B5AGRAWEB/wHlAZUBZgH/AeYBmgFsAf8B5wGcAXAB/wHoAZ4BcwH/AekBoAF0Af8B6QGgAXUB/wHp
Af8B6AGWAW8B/wHoAZkBcQH/AegBmQFyAf8B6QGbAXUB/wH3Ad4B0xn/A/4B/wHqAaQBgwH/AegBlQFv AaABdQH/AfABxQGwAf8D/hX/AfgB5AHaAf8B6QGgAXQB/wHoAZ4BcgH/AecBnAFvAf8B5gGaAWsB/wHl
Af8B5wGUAWwB/wHmAZEBaAH/AeQBjQFhAf8B4gGBAVEB/wHfAXABOgH/AdoBWwEeAf8BrAE/ARIB/wEf AZUBZgH/AeQBkQFgAf8B4gGLAVoB/wHdAWUBMQH/AawBNwEKAf8DHAEnBAADOQFfA0kB/wNjAf8DZwH/
ARwBGwEnBAADdQHCA1cB/wN2Af8DeQH/A3oB/wN6Af8DfAH/A4EB/wOEAf8DhgH/A4gB/wOJAf8DiwH/ A2sB/wOFAf8DiAH/A4gB/wOJAf8DigH/A4sB/wOMAf8DjQH/A44B/wOOAf8DjgH/A44B/wOOAf8DjgH/
A4wB/wOMAf8DjAH/A4wB/wOMAf8DjAH/A4sB/wOJAf8DiAH/A4YB/wOEAf8DgQH/A3sB/wN6Af8DeQH/ A40B/wOMAf8DiwH/A4kB/wOIAf8DiAH/A4gB/wOEAf8DawH/A2cB/wNkAf8DRQH/A0kBhwM5AV8CAAGD
A3gB/wN2Af8DWgH/A3YB0gJaAYMBwgIBAZIB/wIsAasB/wEvATABrwH/ATABMwGvAf8BMAE0Aa8B/wEy Af8BGQEYAaAB/wIdAaMB/wEiASQBpgH/ATsBPQGxAf8BQAFDAbQB/wE/AUMBtAH/AUEBRQG1Af8BQgFH
ATgBsQH/ATgBPgG0Af8BPAFDAbYB/wE/AUcBuAH/AUIBSwG6Af8BRAFNAboB/wFGAVABvAH/AUcBUgG9 AbYB/wFDAUkBtwH/AUUBSwG3Af8BRgFMAbgB/wFHAU0BuQH/AUcBTgG5Af8BSAFOAbkB/wFIAU4BuQH/
Af8BRwFTAb0B/wFIAVMBvAH/AUgBUwG9Af8BRwFSAb0B/wFHAVIBvQH/AUYBUAG8Af8BQwFMAboB/wFC AUcBTgG5Af8BRwFNAbgB/wFGAUsBtwH/AUUBSgG3Af8BQwFJAbYB/wFBAUcBtQH/AUABRQG0Af8BPwFC
AUoBuQH/AT8BRwG4Af8BPAFCAbYB/wE4AT4BtAH/ATIBNwGwAf8BMAEzAa8B/wEvATIBrgH/AS4BMAGu AbQB/wFAAUMBtAH/AToBPAGwAf8BIgEkAaUB/wIdAaMB/wIZAaAB/wIAAXUB/wNJAYcEAAMXASADRwGA
Af8CKwGrAf8CBAGVAf8CVgGGAdIEAAMYASADTwGAA1cBgANdAYADXgGAA18BgANfAYADYAGAA2EBgANh A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
AYADYQGAA2EBgANhAYADcQGAA3UBgAN1AYADdQGAA3UBgAN1AYADcgGAA2EBgANgAYADYAGAA18BgANf A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAAw8BFAgAAykBPwG5
AYADXgGAA1wBgANXAYADTQGAAw8BFAgAATABKgEoAT8BugFFARMB/wHcAWkBMwH/AeIBhwFcAf8B4wGO AT0BCwH/Ad4BagE4Af8B4wGSAWIB/wHkAZUBaAH/AeUBmAFuAf8B5wGcAXIB/wHnAZ8BdQH/AegBoQGA
AWMB/wHlAZIBaQH/AeYBlQFtAf8B5wGYAXIB/wHoAZoBdQH/AegBnAF2Af8B6AGcAXcB/wHoAZwBdwH/ Af8B6QGiAYEB/wHtAbkBoQH/AfUB2wHPAf8D/hn/AfgB4wHaAf8B6QGkAYMB/wHoAaABdgH/AecBnwF0
AegBnwF7Af8B+gHtAecV/wH8AfQB8QH/AegBmwF3Af8B6AGZAXQB/wHnAZcBcQH/AeYBlQFsAf8B5QGS Af8B5gGcAXEB/wHlAZgBbQH/AeQBlQFnAf8B4wGRAWEB/wHdAWkBNwH/AasBNwEKAf8DHAEnBAADDgET
AWkB/wHjAY0BYgH/AeEBhwFbAf8B3AFpATIB/wGsAT8BEgH/AR8BHAEbAScEAANpAZoDUQH/A3IB/wN2 A1kB/wNVAf8DYQH/A3IB/wOMAf8DhwH/A4kB/wOLAf8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjwH/
Af8DdAH/A3gB/wOCAf8DhwH/A4kB/wOKAf8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjgH/A40B/wON A48B/wOPAf8DjgH/A44B/wONAf8DjQH/A4sB/wOKAf8DiQH/A4cB/wOLAf8DcgH/A2AB/wNXAf8DUAH/
Af8DjAH/A4wB/wOKAf8DiQH/A4gB/wOGAf8DgQH/A3cB/wNzAf8DdQH/A3EB/wNSAf8DdAG8AlwBbwGa AyMBNAMOARMCFQGLAf8CBgGTAf8CFQGcAf8CLwGnAf8BSAFJAbMB/wFBAUMBsQH/AUMBRgGyAf8BRQFI
AgABhwH/AigBpgH/ASwBLQGqAf8BKAErAakB/wEuATIBrAH/AT0BQQGyAf8BQwFHAbUB/wFFAUoBtwH/ AbQB/wFGAUoBtAH/AUcBTAG1Af8BSAFNAbUB/wFJAU4BtgH/AUoBTwG2Af8BSgFQAbcB/wFLAVABtwH/
AUcBTQG4Af8BSQFPAbkB/wFKAVIBugH/AUsBUwG7Af8BTAFUAbwB/wFMAVQBvAH/AU0BVQG8Af8BTQFV AUsBUAG3Af8BSwFPAbcB/wFKAVABtwH/AUkBTgG2Af8BSAFNAbUB/wFIAUwBtQH/AUYBSgG0Af8BRQFI
AbwB/wFMAVQBuwH/AUwBUwG7Af8BSwFSAboB/wFKAVEBugH/AUgBTwG4Af8BRgFNAbcB/wFEAUkBtgH/ AbIB/wFDAUYBsgH/AUEBQwGxAf8BSAFKAbIB/wIvAacB/wEUARUBnAH/AggBlQH/AgwBggH/AyMBNAQA
AUIBRgG1Af8BPAFAAbEB/wEuATEBqwH/ASgBKgGoAf8CLAGpAf8BKAEnAaUB/wIAAYoB/wJgAXwBvAQA AxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
AxgBIANPAYADWAGAA14BgANfAYADXwGAA2ABgANhAYADYQGAA2IBgANiAYADYgGAA2kBgAN0AYADdQGA A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEU
A3UBgAN1AYADdQGAA3UBgANvAYADYgGAA2EBgANhAYADYAGAA18BgANeAYADXQGAA1gBgANNAYADDwEU CAADKQE/AbkBPQELAf8B3gFuAT0B/wHjAZYBaQH/AeUBmgFuAf8B5gGeAXMB/wHnAaEBgAH/AekBqgGN
CAABMAEqASgBPwG6AUUBEwH/Ad0BbQE7Af8B4gGLAWMB/wHkAZEBaQH/AeUBlQFuAf8B5gGaAXQB/wHn Af8B8QHNAbwB/wH6AfAB7AH/A/4l/wH4AeYB3gH/AekBqQGNAf8B5wGiAYIB/wHmAaEBdgH/AeYBngFz
AZwBeAH/AegBngF7Af8B6QGgAXwB/wHpAaABfQH/AekBoAF9Af8B8AHFAbAB/wP+Ff8B+AHkAdoB/wHp Af8B5AGaAW0B/wHjAZYBaAH/Ad0BbQE8Af8BqwE2AQoB/wMcAScIAANeAd0DRwH/A1sB/wN0Af8DjwH/
AaABfAH/AegBngF6Af8B5wGcAXcB/wHmAZoBcwH/AeUBlQFuAf8B5AGRAWgB/wHiAYsBYgH/Ad0BbQE5 A4oB/wOMAf8DjAH/A44B/wOOAf8DjwH/A5AB/wOQAf8DkAH/A5AB/wOQAf8DkAH/A5AB/wOQAf8DjwH/
Af8BrAE/ARIB/wEfARwBGwEnBAADSAFfA1EB/wNrAf8DbwH/A3MB/wOFAf8DiAH/A4gB/wOJAf8DigH/ A44B/wOOAf8DjAH/A4sB/wOKAf8DjwH/A3QB/wNaAf8DSAH/A2IB7wgAAlgBYwHdAgABgwH/AhABlQH/
A4sB/wOMAf8DjQH/A44B/wOOAf8DjgH/A44B/wOOAf8DjgH/A40B/wOMAf8DiwH/A4kB/wOIAf8DiAH/ ATUBNAGnAf8CUAGzAf8BSAFJAbAB/wFJAUoBsgH/AUsBTAGyAf8BTQFOAbMB/wFNAVABtAH/AU4BUQG0
A4gB/wOEAf8DcwH/A28B/wNsAf8DTQH/A2MBhwJGAUkBXwIDAYMB/wEhASABoAH/AiUBowH/ASoBLAGm Af8BTwFTAbUB/wFPAVMBtQH/AU8BUwG1Af8BUAFTAbUB/wFQAVMBtQH/AU8BUwG1Af8BTwFTAbUB/wFP
Af8BQwFFAbEB/wFIAUsBtAH/AUcBSwG0Af8BSQFNAbUB/wFKAU8BtgH/AUsBUQG3Af8BTQFTAbcB/wFO AVMBtQH/AU4BUQG0Af8BTQFPAbQB/wFNAU4BswH/AUsBTAGyAf8BSQFKAbEB/wFIAUkBsAH/AlABswH/
AVQBuAH/AU8BVQG5Af8BTwFWAbkB/wFQAVYBuQH/AVABVgG5Af8BTwFWAbkB/wFPAVUBuAH/AU4BUwG3 AjQBpwH/Ag8BlAH/AgABhAH/AkwBaQHvCAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
Af8BTQFSAbcB/wFLAVEBtgH/AUkBTwG1Af8BSAFNAbQB/wFHAUoBtAH/AUgBSwG0Af8BQgFEAbAB/wEq AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
ASwBpQH/AiUBowH/AiEBoAH/AgABfQH/Al0BZAGHBAADGAEgA04BgANZAYADXwGAA18BgANgAYADYQGA AYADRwGAA0cBgANHAYADRwGAA0cBgAMPARQIAAMpAT8BuAE+AQsB/wHeAXIBQgH/AeQBmgFvAf8B5gGf
A2EBgANiAYADYgGAA2cBgANtAYADdAGAA3UBgAN1AYADdQGAA3UBgAN1AYADdQGAA28BgANiAYADYgGA
A2EBgANhAYADYAGAA18BgANeAYADWAGAA00BgAMPARQIAAEwASoBKAE/AbkBRQETAf8B3gFyAUAB/wHj
AZIBagH/AeQBlQFwAf8B5QGYAXYB/wHnAZwBegH/AecBnwF9Af8B6AGhAYAB/wHpAaIBgQH/Ae0BuQGh
Af8B9QHbAc8B/wP+Gf8B+AHjAdoB/wHpAaQBgwH/AegBoAF+Af8B5wGfAXwB/wHmAZwBeQH/AeUBmAF1
Af8B5AGVAW8B/wHjAZEBaQH/Ad0BcQE/Af8BqwE/ARIB/wEfARwBGwEnBAADDwETA2EB/wNdAf8DaQH/
A3oB/wOMAf8DhwH/A4kB/wOLAf8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjwH/A48B/wOPAf8DjgH/
A44B/wONAf8DjQH/A4sB/wOKAf8DiQH/A4cB/wOLAf8DegH/A2gB/wNfAf8DWAH/AyoBNAMPARMCHQGL
Af8CDgGTAf8CHQGcAf8CNwGnAf8BUAFRAbMB/wFJAUsBsQH/AUsBTgGyAf8BTQFQAbQB/wFOAVIBtAH/
AU8BVAG1Af8BUAFVAbUB/wFRAVYBtgH/AVIBVwG2Af8BUgFYAbcB/wFTAVgBtwH/AVMBWAG3Af8BUwFX
AbcB/wFSAVgBtwH/AVEBVgG2Af8BUAFVAbUB/wFQAVQBtQH/AU4BUgG0Af8BTQFQAbIB/wFLAU4BsgH/
AUkBSwGxAf8BUAFSAbIB/wI3AacB/wEcAR0BnAH/AhABlQH/AhQBggH/AyoBNAQAAxgBIANOAYADWQGA
A18BgANgAYADYQGAA2IBgANkAYADagGAA3IBgAN0AYADdQGAA3UBgAN1AYADdQGAA3UBgAN1AYADdQGA
A3UBgAN1AYADcAGAA2QBgANiAYADYQGAA2EBgANgAYADXwGAA1kBgANNAYADDwEUCAABMAEqASgBPwG5
AUUBEwH/Ad4BdgFFAf8B4wGWAXEB/wHlAZoBdgH/AeYBngF7Af8B5wGhAYAB/wHpAaoBjQH/AfEBzQG8
Af8B+gHwAewB/wP+Jf8B+AHmAd4B/wHpAakBjQH/AecBogGCAf8B5gGhAX4B/wHmAZ4BewH/AeQBmgF1
Af8B4wGWAXAB/wHdAXUBRAH/AasBPgESAf8BHwEcARsBJwgAA2oB3QNPAf8DYwH/A3wB/wOPAf8DigH/
A4wB/wOMAf8DjgH/A44B/wOPAf8DkAH/A5AB/wOQAf8DkAH/A5AB/wOQAf8DkAH/A5AB/wOPAf8DjgH/
A44B/wOMAf8DiwH/A4oB/wOPAf8DfAH/A2IB/wNQAf8DaQHvCAACQAGCAd0CAAGDAf8CGAGVAf8BPQE8
AacB/wJYAbMB/wFQAVEBsAH/AVEBUgGyAf8BUwFUAbIB/wFVAVYBswH/AVUBWAG0Af8BVgFZAbQB/wFX
AVsBtQH/AVcBWwG1Af8BVwFbAbUB/wFYAVsBtQH/AVgBWwG1Af8BVwFbAbUB/wFXAVsBtQH/AVcBWwG1
Af8BVgFZAbQB/wFVAVcBtAH/AVUBVgGzAf8BUwFUAbIB/wFRAVIBsQH/AVABUQGwAf8CWAGzAf8CPAGn
Af8CFwGUAf8CAAGEAf8COQGEAe8IAAMYASADTgGAA1oBgANgAYADYgGAA2cBgANuAYADdAGAA3UBgAN1
AYADdQGAA3UBgAN1AYADdQGAA3QBgAN0AYADcgGAA3MBgAN1AYADdQGAA3UBgANxAYADZAGAA2IBgANi
AYADYQGAA2ABgANaAYADTQGAAw8BFAgAATABKgEoAT8BuAFGARMB/wHeAXoBSgH/AeQBmgF3Af8B5gGf
AYAB/wHsAb0BpgH/AfYB4AHVAf8B/gL8Gf8D/gH/Af4B/QH8Af8B+gHyAe4B/wH8AfcB9A3/AfkB7gHp AYAB/wHsAb0BpgH/AfYB4AHVAf8B/gL8Gf8D/gH/Af4B/QH8Af8B+gHyAe4B/wH8AfcB9A3/AfkB7gHp
Af8B6QGuAZIB/wHnAaMBhQH/AeYBogGBAf8B5QGdAXwB/wHkAZkBdgH/Ad4BegFKAf8BqwE+ARIB/wEf Af8B6QGuAZIB/wHnAaMBhQH/AeYBogGBAf8B5QGdAXQB/wHkAZkBbgH/Ad4BcgFCAf8BqwE2AQoB/wMc
ARwBGwEnCAADUgFwA0cB/wNZAf8DeQH/A5UB/wOMAf8DjgH/A48B/wOQAf8DkAH/A5IB/wOSAf8DkgH/ AScIAANAAXADPwH/A1EB/wNxAf8DlQH/A4wB/wOOAf8DjwH/A5AB/wOQAf8DkgH/A5IB/wOSAf8DkgH/
A5IB/wOTAf8DkwH/A5IB/wOSAf8DkgH/A5EB/wOQAf8DjwH/A48B/wOOAf8DjAH/A5QB/wN5Af8DWgH/ A5MB/wOTAf8DkgH/A5IB/wOSAf8DkQH/A5AB/wOPAf8DjwH/A44B/wOMAf8DlAH/A3EB/wNSAf8DPgH/
A0YB/wNiAYoIAAJNAVQBcAIAAXEB/wIKAY0B/wI6AaQB/wJhAbYB/wJWAa8B/wJXAbEB/wFYAVkBsgH/ A0oBiggAA0ABcAIAAWkB/wICAY0B/wIyAaQB/wJZAbYB/wJOAa8B/wJPAbEB/wFQAVEBsgH/AVEBUgGz
AVkBWgGzAf8BWgFbAbMB/wFcAV0BtAH/AVwBXgG0Af8BXAFeAbUB/wFcAV8BtAH/AV0BXwG1Af8BXQFf Af8BUgFTAbMB/wFUAVUBtAH/AVQBVgG0Af8BVAFWAbUB/wFUAVcBtAH/AVUBVwG1Af8BVQFXAbUB/wFU
AbUB/wFcAV8BtAH/AVwBXgG0Af8BXAFfAbQB/wFbAV0BtAH/AVoBWwGzAf8BWQFaAbIB/wJYAbIB/wJX AVcBtAH/AVQBVgG0Af8BVAFXAbQB/wFTAVUBtAH/AVIBUwGzAf8BUQFSAbIB/wJQAbIB/wJPAbEB/wFP
AbEB/wFXAVYBrwH/AmEBtQH/AjoBpAH/AgsBjgH/AgABbgH/AlsBZAGKCAADGAEgA04BgANbAYADaAGA AU4BrwH/AlkBtQH/AjIBpAH/AgMBjgH/AgABZgH/A0oBiggAAxcBIANHAYADRwGAA0cBgANHAYADRwGA
A3IBgAN0AYADdQGAA3UBgAN1AYADdQGAA3QBgANzAYADbwGAA2wBgANoAYADZQGAA2QBgANmAYADcgGA A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A3UBgAN1AYADdQGAA3EBgANjAYADYgGAA2IBgANhAYADWwGAA0wBgAMPARQIAAEwASoBKAE/AbYBRAET A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AbYBPAELAf8B3wF1AUgB/wHt
Af8B3wF9AVAB/wHtAcIBrwH/AfsB9AHxAf8D/hH/Af4C/QH/AfwB9wH1Af8B9gHhAdkB/wHzAdMBxwH/ AcIBrwH/AfsB9AHxAf8D/hH/Af4C/QH/AfwB9wH1Af8B9gHhAdkB/wHzAdMBxwH/Ae0BvgGrAf8B6gGv
Ae0BvgGrAf8B6gGvAZcB/wHoAakBjwH/AesBtQGfAf8B+wH0AfEN/wH5AewB5gH/AegBqgGNAf8B5gGl AZcB/wHoAakBjwH/AesBtQGfAf8B+wH0AfEN/wH5AewB5gH/AegBqgGNAf8B5gGlAYYB/wHlAaEBgwH/
AYYB/wHlAaEBgwH/AeQBngF9Af8B3gF7AU8B/wGpAT0BEgH/AR8BHAEbAScMAANlAegDSAH/A3EB/wOb AeQBngF1Af8B3gFzAUcB/wGpATUBCgH/AxwBJwwAA18B6ANAAf8DaQH/A5sB/wOQAf8DkQH/A5EB/wOS
Af8DkAH/A5EB/wORAf8DkgH/A5MB/wOUAf8DlAH/A5QB/wOUAf8DlQH/A5UB/wOUAf8DlAH/A5QB/wOU Af8DkwH/A5QB/wOUAf8DlAH/A5QB/wOVAf8DlQH/A5QB/wOUAf8DlAH/A5QB/wOTAf8DkgH/A5EB/wOR
Af8DkwH/A5IB/wORAf8DkQH/A5AB/wObAf8DcQH/A0kB/wNfAfMDBAEGDAACNQF/AegCAAFyAf8CMAGc Af8DkAH/A5sB/wNpAf8DQQH/A18B8wMEAQYMAAJVAWcB6AIAAWoB/wIoAZwB/wJjAbkB/wJVAbAB/wJV
Af8CawG5Af8CXQGwAf8CXQGxAf8CXwGxAf8CXwGyAf8BYAFhAbMB/wFhAWIBtAH/AWEBYgG0Af8BYQFj AbEB/wJXAbEB/wJXAbIB/wFYAVkBswH/AVkBWgG0Af8BWQFaAbQB/wFZAVsBtAH/AVoBWwG0Af8BWgFc
AbQB/wFiAWMBtAH/AWIBZAG1Af8BYgFkAbUB/wFiAWQBtAH/AWEBYgG0Af8BYQFiAbQB/wFhAWIBtAH/ AbUB/wFaAVwBtQH/AVoBXAG0Af8BWQFaAbQB/wFZAVoBtAH/AVkBWgG0Af8BWAFZAbMB/wJXAbIB/wJX
AWABYQGzAf8CXwGyAf8CXwGxAf8BXgFdAbEB/wJdAbAB/wJrAbgB/wIwAZwB/wIAAXQB/wIsAX0B8wME AbEB/wFWAVUBsQH/AlUBsAH/AmMBuAH/AigBnAH/AgABbAH/AkQBbAHzAwQBBggAAxcBIANHAYADRwGA
AQYIAAMYASADTgGAA1sBgANkAYADcQGAA3QBgAN0AYADcAGAA20BgANqAYADZgGAA2QBgANkAYADZAGA A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A2QBgANkAYADZAGAA2QBgANmAYADcgGAA3UBgAN1AYADdQGAA28BgANjAYADYwGAA2IBgANbAYADTAGA A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AbMBPAEL
Aw8BFAgAATABKgEoAT8BswFEARMB/wHeAYIBVgH/AecBrAGRAf8B+QHtAegB/wP+Af8B/QH6AfkB/wH4 Af8B3gGCAU4B/wHnAawBkQH/AfkB7QHoAf8D/gH/Af0B+gH5Af8B+AHqAeQB/wH0AdkBzQH/Ae8BygG4
AeoB5AH/AfQB2QHNAf8B7wHKAbgB/wHqAbYBngH/AekBsAGUAf8B6AGuAZIB/wHoAa4BkgH/AegBrgGS Af8B6gG2AZ4B/wHpAbABlAH/AegBrgGSAf8B6AGuAZIB/wHoAa4BkgH/AegBrgGSAf8B6AGuAZIB/wHo
Af8B6AGuAZIB/wHoAa4BkgH/AegBrgGSAf8B6wG4AaAB/wH6AfIB7g3/AfYB4QHYAf8B5gGnAYsB/wHm Aa4BkgH/AesBuAGgAf8B+gHyAe4N/wH2AeEB2AH/AeYBpwGLAf8B5gGlAYgB/wHlAaMBhAH/Ad4BgQFN
AaUBiAH/AeUBowGEAf8B3gGBAVUB/wGmAT0BEQH/AR8CHAEnDAADQgFVA0IB/wNVAf8DjgH/A54B/wOT Af8BpgE1AQkB/wMcAScMAAM1AVUDOgH/A00B/wOOAf8DngH/A5MB/wOVAf8DlgH/A5YB/wOXAf8DlwH/
Af8DlQH/A5YB/wOWAf8DlwH/A5cB/wOXAf8DmAH/A5gB/wOYAf8DlwH/A5cB/wOXAf8DlgH/A5YB/wOV A5cB/wOYAf8DmAH/A5gB/wOXAf8DlwH/A5cB/wOWAf8DlgH/A5UB/wOVAf8DkwH/A50B/wOPAf8DTgH/
Af8DlQH/A5MB/wOdAf8DjwH/A1YB/wM/Af8DUAFoEAACQAFDAVUCAAFmAf8CDAGCAf8CWwGuAf8CcQG6 AzcB/wM9AWgQAAM1AVUCAAFeAf8CBAGCAf8CUwGuAf8CaQG6Af8CWgGyAf8CXQGzAf8CXgGzAf8BXgFf
Af8CYgGyAf8CZQGzAf8CZgGzAf8BZgFnAbMB/wJnAbQB/wJnAbQB/wFnAWgBtAH/AWgBaQG1Af8BaAFp AbMB/wJfAbQB/wJfAbQB/wFfAWABtAH/AWABYQG1Af8BYAFhAbUB/wFgAWEBtQH/AV8BYQG1Af8BXwFg
AbUB/wFoAWkBtQH/AWcBaQG1Af8BZwFoAbQB/wJnAbQB/wFmAWcBswH/AWYBZwGzAf8CZgGyAf8CZQGy AbQB/wJfAbQB/wFeAV8BswH/AV4BXwGzAf8CXgGyAf8CXQGyAf8CWgGxAf8CaQG5Af8CVAGvAf8CBAGE
Af8CYgGxAf8CcQG5Af8CXAGvAf8CDAGEAf8CAAFgAf8CTQFQAWgMAAMYASADTQGAA1wBgANjAYADZAGA Af8CAAFYAf8DPQFoDAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
A2YBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZgGA AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
A3EBgAN1AYADdQGAA3UBgANtAYADYwGAA2MBgANcAYADSwGAAw8BFAgAATABKgEoAT8BsAFDARMB/wHd AYADRwGAA0cBgAMPARQIAAMpAT8BsAE7AQsB/wHdAYQBVAH/AeUBpAGKAf8B5wGsAZQB/wHpAbkBogH/
AYQBXAH/AeUBpAGKAf8B5wGsAZQB/wHpAbkBogH/AecBsAGVAf8B5wGvAZIB/wHoAa8BkwH/AegBsQGV AecBsAGVAf8B5wGvAZIB/wHoAa8BkwH/AegBsQGVAf8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6AGw
Af8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6AGwAZUB/wHo AZUB/wHoAbABlQH/AegBsAGVAf8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6gG3AZ8B/wH6AfAB6w3/
AbABlQH/AegBsAGVAf8B6gG3AZ8B/wH6AfAB6w3/AfMB2QHNAf8B5gGmAYwB/wHlAaQBiQH/Ad0BhAFb AfMB2QHNAf8B5gGmAYwB/wHlAaQBiQH/Ad0BhAFTAf8BowE1AQkB/wMcAScQAANSAaEDOAH/A1oB/wOo
Af8BowE9AREB/wEfAhwBJxAAA2cBoQNAAf8DYgH/A6gB/wOhAf8DmQH/A5oB/wOaAf8DmwH/A5sB/wOb Af8DoQH/A5kB/wOaAf8DmgH/A5sB/wObAf8DmwH/A5sB/wObAf8DmwH/A5sB/wObAf8DmgH/A5oB/wOa
Af8DmwH/A5sB/wObAf8DmwH/A5sB/wOaAf8DmgH/A5oB/wOaAf8DmQH/A6AB/wOoAf8DZAH/Az4B/wNt Af8DmgH/A5kB/wOgAf8DqAH/A1wB/wM2Af8DVgG2GAADUgGhAgABWQH/AhgBjAH/AoIBwAH/Am8BugH/
AbYYAAJYAW4BoQIAAWEB/wIgAYwB/wKCAcAB/wJ3AboB/wJrAbQB/wJuAbUB/wJuAbUB/wJuAbYB/wFv AmMBtAH/AmYBtQH/AmYBtQH/AmYBtgH/AWcBZgG2Af8CZwG2Af8CZwG2Af8CZwG2Af8CZwG2Af8CZwG2
AW4BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/AW8BbgG1Af8CbgG1Af8CbgG1 Af8CZwG2Af8BZwFmAbUB/wJmAbUB/wJmAbUB/wJmAbUB/wJjAbQB/wJuAbkB/wKCAcAB/wIZAY4B/wIA
Af8CbgG1Af8CawG0Af8CdgG5Af8CggHAAf8CIQGOAf8CAAFcAf8CWQF2AbYQAAMYASADTQGAA10BgANj AVQB/wNWAbYQAAMXASADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADZAGAA2QBgANkAYADZAGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANl AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADZQGAA2YBgANxAYADdQGAA3UBgAN0AYADZQGAA2MBgANdAYADSwGAAw8BFAgAATABKgEoAT8BrgFD AYADRwGAAw8BFAgAAykBPwGuATsBCwH/Ad0BigFcAf8B5QGoAY4B/wHmAaoBkAH/AeYBrQGSAf8B5wGu
ARMB/wHdAYoBZAH/AeUBqAGOAf8B5gGqAZAB/wHmAa0BkgH/AecBrgGUAf8B5wGuAZYB/wHoAa8BlwH/ AZQB/wHnAa4BlgH/AegBrwGXAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/
AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGv AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AekBtQGgAf8B+AHs
AZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHpAbUBoAH/AfgB7AHnCf8D/gH/AecBrwGYAf8B5QGo AecJ/wP+Af8B5wGvAZgB/wHlAagBjQH/AdwBigFbAf8BogE1AQkB/wMcAScTAAEBA14B1QM7Af8DZAH/
AY0B/wHcAYoBYwH/AaIBPQERAf8BHwIcAScTAAEBA10B1QNDAf8DbAH/A7IB/wOuAf8DnwH/A6EB/wOh A7IB/wOuAf8DnwH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/
Af8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A58B/wOrAf8DswH/A28B/wM9 A58B/wOrAf8DswH/A2cB/wM1Af8DXwHjAwYBCBsAAQECWgFeAdUCAAFfAf8CJwGRAf8CkQHHAf8CiwHD
Af8DXwHjAwYBCBsAAQECNwFwAdUCAAFnAf8CLwGRAf8CkQHHAf8CiwHDAf8CdQG3Af8CeAG5Af8CeQG5 Af8CbQG3Af8CcAG5Af8CcQG5Af8CcQG6Af8CcQG5Af8CcQG5Af8CcQG5Af8CcQG5Af8CcQG5Af8CcQG5
Af8CeQG6Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeAG5 Af8CcQG5Af8CcQG5Af8CcAG5Af8CbQG3Af8CiAHBAf8CkgHIAf8CKwGVAf8CAAFTAf8CWAFfAeMDBgEI
Af8CdQG3Af8CiAHBAf8CkgHIAf8CMwGVAf8CAAFbAf8CNwFzAeMDBgEIEAADGAEgA00BgANcAYADZAGA EAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
A2QBgANkAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGA AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMP
A2UBgANlAYADZgGAA3ABgAN0AYADcgGAA2UBgANkAYADXAGAA0sBgAMPARQIAAEwASoBKAE/AawBQgES ARQIAAMpAT8BrAE6AQoB/wHbAYkBWQH/AeUBqQGSAf8B5gGsAZQB/wHmAa8BlgH/AeYBsAGYAf8B5wGx
Af8B2wGJAWEB/wHlAakBkgH/AeYBrAGUAf8B5gGvAZYB/wHmAbABmAH/AecBsQGZAf8B5wGwAZoB/wHn AZkB/wHnAbABmgH/AecBsgGaAf8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/
AbIBmgH/AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/AecBswGb AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGzAZsB/wHnAbIBmgH/AegBtAGeAf8B+AHq
Af8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGyAZoB/wHoAbQBngH/AfgB6gHjAf8D/gH/AfoB8gHv AeMB/wP+Af8B+gHyAe8B/wHmAa8BmAH/AeUBqQGRAf8B2gGKAVkB/wGhATQBCQH/AxwBJxQAAw0BEgNY
Af8B5gGvAZgB/wHlAakBkQH/AdoBigFhAf8BoQE8AREB/wEfARwBGwEnFAADDgESA1AB0QM/Af8DZAH/ AdEDNwH/A1wB/wOpAf8DwgH/A7EB/wOnAf8DqAH/A6kB/wOpAf8DqQH/A6kB/wOpAf8DqQH/A6gB/wOn
A6kB/wPCAf8DsQH/A6cB/wOoAf8DqQH/A6kB/wOpAf8DqQH/A6kB/wOpAf8DqAH/A6cB/wOvAf8DwQH/ Af8DrwH/A8EB/wOuAf8DXwH/AzEB/wNcAdwDFAEbIAADDQESAlgBXAHRAgABVwH/Ah8BiQH/AoUBvwH/
A64B/wNnAf8DOQH/A10B3AMWARsgAAMOARICKAFhAdECAAFfAf8CJwGJAf8ChQG/Af8CqAHSAf8CkQHE AqgB0gH/ApEBxAH/AoMBvAH/AoUBvQH/AoUBvgH/AoYBvgH/AoYBvgH/AoYBvgH/AoYBvgH/AoYBvgH/
Af8CgwG8Af8ChQG9Af8ChQG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChQG9Af8CgwG8 AoUBvQH/AoMBvAH/Ao4BwgH/AqcB0QH/AosBwwH/AiQBiwH/AgABSgH/AlgBYQHcAxQBGxQAAxEBFwNH
Af8CjgHCAf8CpwHRAf8CiwHDAf8CLAGLAf8CAAFSAf8COQFuAdwDFgEbFAADEQEXA0sBgANXAYADZAGA AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
A2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGA AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANGAX4DBgEICAADHwEt
A2UBgANlAYADZQGAA2UBgANnAYADZgGAA2UBgANkAYADVwGAA0kBfgMGAQgIAAEjASABHwEtAZ4BOwER AZ4BMwEJAf8B0QFmATYB/wHmAasBlwH/AeYBsAGZAf8B5gGzAZoB/wHnAbQBmwH/AecBtQGcAf8B5wG2
Af8B0QFuAT4B/wHmAasBlwH/AeYBsAGZAf8B5gGzAZoB/wHnAbQBmwH/AecBtQGcAf8B5wG2AZwB/wHn AZwB/wHnAbYBnAH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/
AbYBnAH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGd AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG1AZwB/wHnAbYBnAH/AecBtwGeAf8B6QG/
Af8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG1AZwB/wHnAbYBnAH/AecBtwGeAf8B6QG/AagB/wHn AagB/wHnAbcBnwH/AeYBsAGZAf8B5gGrAZcB/wHRAWcBNgH/AX0BLgEZAfwDDAEQGAADBAEFA1MBpQMx
AbcBnwH/AeYBsAGZAf8B5gGrAZcB/wHRAW8BPgH/AZIBNgERAfwBDQIMARAYAAMEAQUDZQGlAzkB/wNF Af8DPQH/A4QB/wOzAf8DygH/A8QB/wO9Af8DtQH/A7MB/wOzAf8DtAH/A7sB/wPEAf8DygH/A7oB/wOM
Af8DhAH/A7MB/wPKAf8DxAH/A70B/wO1Af8DswH/A7MB/wO0Af8DuwH/A8QB/wPKAf8DugH/A4wB/wNF Af8DPQH/Ay0B/wNWAbEDCgENKAADBAEFA1MBpQIAAUsB/wIAAWQB/wJMAaAB/wKVAcUB/wK0AdcB/wKt
Af8DNQH/A2kBsQMKAQ0oAAMEAQUCVQFtAaUCAAFTAf8CAAFsAf8CVAGgAf8ClQHFAf8CtAHXAf8CrQHT AdMB/wKiAc0B/wKYAccB/wKUAcUB/wKUAcUB/wKXAcYB/wKgAcsB/wKsAdIB/wK1AdcB/wKeAcoB/wJW
Af8CogHNAf8CmAHHAf8ClAHFAf8ClAHFAf8ClwHGAf8CoAHLAf8CrAHSAf8CtQHXAf8CngHKAf8CXgGm AaYB/wIAAWUB/wIAAUEB/wNWAbEDCgENHAADOwFkA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
Af8CAAFtAf8CAAFJAf8CVwFxAbEDCgENHAADPQFkA08BgANXAYADXgGAA2ABgANgAYADYAGAA2EBgANh AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYAGAA2ABgANg AYADRwGAA0cBgANHAYADNQFVEAADWQHHAb8BRQENAf8B0gFoATkB/wHcAZEBaAH/Ad8BnAFxAf8B4AGb
AYADXgGAA1cBgANPAYADNwFVEAABcgE+ASwBxwG/AU0BFQH/AdIBcAFBAf8B3AGRAXAB/wHfAZwBeQH/ AXIB/wHiAZ0BcgH/AeMBnwFzAf8B4wGfAXQB/wHjAaABdAH/AeMBnwF0Af8B4wGfAXQB/wHjAZ8BdAH/
AeABmwF6Af8B4gGdAXoB/wHjAZ8BewH/AeMBnwF8Af8B4wGgAXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGf AeMBnwF0Af8B4wGfAXQB/wHjAZ8BdAH/AeMBnwF0Af8B4wGfAXQB/wHjAaABdAH/AeMBnwFzAf8B4wGf
AXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGfAXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGgAXwB/wHjAZ8BewH/ AXMB/wHiAZwBcgH/AeABmwFyAf8B3gGcAXEB/wHcAZEBaAH/AdIBaAE5Af8BuwFCAQwB/wNSAakkAANC
AeMBnwF7Af8B4gGcAXoB/wHgAZsBegH/Ad4BnAF5Af8B3AGRAXAB/wHSAXABQQH/AbsBSgEUAf8BZwFD AXQDWAHmAzAB/wNCAf8DdQH/A5cB/wOsAf8DvwH/A8YB/wPHAf8DwQH/A7EB/wOaAf8DgwH/A0IB/wMs
ATYBqSQAA00BdANPAeYDOAH/A0oB/wN9Af8DlwH/A6wB/wO/Af8DxgH/A8cB/wPBAf8DsQH/A5oB/wOD Af8DWQHsA0QBezgAA0IBdAJSAWEB5gIAAUgB/wIAAWoB/wJDAZoB/wJnAa4B/wKMAb4B/wKmAc0B/wKw
Af8DSgH/AzQB/wNJAewDWAF7OAACRgFQAXQCHgFnAeYCAAFQAf8CBAFyAf8CSwGaAf8CbwGuAf8CjAG+ AdMB/wKxAdQB/wKpAc8B/wKSAcIB/wJqAbAB/wJKAZ4B/wIAAWoB/wIAAUAB/wJKAWAB7ANEAXskAAML
Af8CpgHNAf8CsAHTAf8CsQHUAf8CqQHPAf8CkgHCAf8CcgGwAf8CUgGeAf8CBAFyAf8CAAFIAf8CGAFg AQ8DOwFkA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AewCUwFaAXskAAMMAQ8DPQFkA0sBgANNAYADTQGAA00BgANOAYADTgGAA04BgANOAYADTgGAA04BgANO AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRgF/AzcBWwMGAQgQAAMVAR0DWQHH
AYADTgGAA04BgANOAYADTgGAA04BgANOAYADTgGAA04BgANOAYADTQGAA00BgANNAYADSgF/AzkBWwMG AZ4BMwEJAf8BrAE6AQoB/wGtATsBCwH/AbABOwELAf8BswE8AQsB/wG2ATwBCwH/AbgBPQELAf8BuQE9
AQgQAAEXARYBFQEdAXIBPgEsAccBngE7AREB/wGsAUIBEgH/Aa0BQwETAf8BsAFDARMB/wGzAUQBEwH/ AQsB/wG5AT0BCwH/AbkBPQELAf8BuQE9AQsB/wG5AT0BCwH/AbkBPQELAf8BuQE9AQsB/wG5AT0BCwH/
AbYBRAETAf8BuAFFARMB/wG5AUUBEwH/AbkBRQETAf8BuQFFARMB/wG5AUUBEwH/AbkBRQETAf8BuQFF AbkBPQELAf8BuQE9AQsB/wG4AT0BCwH/AbYBPAELAf8BswE8AQsB/wGwATsBCwH/Aa0BOwELAf8BrAE6
ARMB/wG5AUUBEwH/AbkBRQETAf8BuQFFARMB/wG5AUUBEwH/AbgBRQETAf8BtgFEARMB/wGzAUQBEwH/ AQoB/wGKAToBEQH+A1UBtQMMARAoAAMKAQ0DTAGQA1oB3gM0Af8DMQH/AzgB/wNGAf8DWgH/A1wB/wNI
AbABQwETAf8BrQFDARMB/wGsAUIBEgH/AZoBOgERAf4BbAFBATIBtQENAgwBECgAAwoBDQNbAZADSQHe Af8DOAH/AzIB/wMyAf8DXgHgA04BlgMNARJAAAMKAQ0DTAGQAlUBXwHeAgABUQH/AgABSwH/AgABWgH/
AzwB/wM5Af8DQAH/A04B/wNiAf8DZAH/A1AB/wNAAf8DOgH/AzoB/wNPAeADYQGWAw4BEkAAAwoBDQJQ AgEBbQH/Ah0BhgH/AiABiAH/AgQBbgH/AgABWQH/AgABTAH/AgABTAH/AlkBXwHgA04BlgMNARIwAAMR
AWABkAIcAV8B3gIAAVkB/wIAAVMB/wIAAWIB/wIJAXUB/wIlAYYB/wIoAYgB/wIMAXYB/wIAAWEB/wIA BBcBIAMXASADFwEgAxcBIAMXASADFwEgAxcBIAMXASADFwEgAxcBIAMXASADFwEgAxcBIAMXASADFwEg
AVQB/wIAAVQB/wIjAWMB4AJXAWUBlgMOARIwAAMSARcDGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEg AxcBIAMXASADFwEgAxcBIAMXASADFwEgAxcBIAMOARMgAAMfAS0DKQE/AykBPwMpAT8DKQE/AykBPwMp
AxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEg AT8DKQE/AykBPwMpAT8DKQE/AykBPwMpAT8DKQE/AykBPwMpAT8DKQE/AykBPwMpAT8DKQE/AykBPwMp
Aw4BEyAAASMBIAEfAS0BMAErASgBPwEwASsBKQE/ATABKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEp AT8DKQE/AxsBJjwAAyoBQANKAYoDWQG+A1kB7ANNAfIDTQHyA1QB7gNaAcADSgGNAywBRFgAAyoBQANK
AT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwEx AYoCVgFZAb4CSgFkAewCOQFZAfICOQFZAfICRgFhAe4CWAFaAcADSgGNAywBRP8ALQABQgFNAT4HAAE+
ASsBKQE/ATEBKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwEwASsBKQE/ATABKwEp AwABKAMAAYADAAFAAwABAQEAAQEGAAEEFgAD//8A/wADAAH/AeABAwL/AeABAwH/AcACAAEDAcACAAED
AT8BMAErASgBPwEeARwBGwEmPAADMAFAA1UBigNYAb4DTAHsA0EB8gNAAfIDTQHuA1wBwANVAY0DNQFE Af8CAAL/AgAB/wGAAgABAwGAAgABAwH+AgABfwH+AgABfwGAAgABAQGAAgABAQH4AgABHwH4AgABHwGA
WAACLgEwAUACSgFaAYoCPAFlAb4CGgFkAewCCgFZAfICCgFZAfICGgFkAe4CQQFpAcACSgFZAY0CMwE1 AgABAQGAAgABAQHwAgABDwHwAgABDwGAAgABAQGAAgABAQHwAgABBwHwAgABBwGAAgABAQGAAgABAQHg
AUT/AC0AAUIBTQE+BwABPgMAASgDAAGAAwABQAMAAQEBAAEBBgABBBYAA///AP8AAwAB/wHgAQMC/wHg AgABBwHgAgABBwGAAgABAQGAAgABAQHAAgABAwHAAgABAwGAAgABAQGAAgABAQGAAgABAQGAAgABAQGA
AQMB/wHAAgABAwHAAgABAwH/AgAC/wIAAf8BgAIAAQMBgAIAAQMB/gIAAX8B/gIAAX8BgAIAAQEBgAIA AgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQgA
AQEB+AIAAR8B+AIAAR8BgAIAAQEBgAIAAQEB8AIAAQ8B8AIAAQ8BgAIAAQEBgAIAAQEB8AIAAQcB8AIA AYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIA
AQcBgAIAAQEBgAIAAQEB4AIAAQcB4AIAAQcBgAIAAQEBgAIAAQEBwAIAAQMBwAIAAQMBgAIAAQEBgAIA AQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGA
AQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIA AgABAQgAAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEB
AQEBgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGA AYACAAEBAcACAAEBAcACAAEBAYACAAEBAYACAAEBAcACAAEDAcACAAEDAYACAAEBAYACAAEBAeACAAEH
AgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEB AeACAAEHAYACAAEBAYACAAEBAeACAAEHAeACAAEHAYACAAEBAYACAAEBAfACAAEPAfACAAEPAYACAAEB
AYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGA AYACAAEBAfgCAAEfAfgCAAEfAcACAAEDAcACAAEDAf4CAAF/Af4CAAF/AcACAAEDAcACAAEDAf8CAAL/
AgABAQGAAgABAQGAAgABAQGAAgABAQHAAgABAQHAAgABAQGAAgABAQGAAgABAQHAAgABAwHAAgABAwGA AgAB/wHwAgABDwHwAgABDwH/AeABBwL/AeABBwn/Cw==
AgABAQGAAgABAQHgAgABBwHgAgABBwGAAgABAQGAAgABAQHgAgABBwHgAgABBwGAAgABAQGAAgABAQHw
AgABDwHwAgABDwGAAgABAQGAAgABAQH4AgABHwH4AgABHwHAAgABAwHAAgABAwH+AgABfwH+AgABfwHA
AgABAwHAAgABAwH/AgAC/wIAAf8B8AIAAQ8B8AIAAQ8B/wHgAQcC/wHgAQcJ/ws=
</value> </value>
</data> </data>
<metadata name="FolderBrowserDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>154, 17</value>
</metadata>
<metadata name="TryConnectTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>319, 17</value>
</metadata>
</root> </root>

View File

@ -3,7 +3,6 @@ using System.Windows.Forms;
using SimConnect; using SimConnect;
using SimConnect.Concrete; using SimConnect.Concrete;
using SimpleInjector; using SimpleInjector;
using SimpleInjector.Diagnostics;
namespace FSFlightLogger namespace FSFlightLogger
{ {

126
FSFlightLogger/Resources/i18n.Designer.cs generated Normal file
View File

@ -0,0 +1,126 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FSFlightLogger.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class i18n {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal i18n() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FSFlightLogger.Resources.i18n", typeof(i18n).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Flight logs.
/// </summary>
internal static string DefaultFolderName {
get {
return ResourceManager.GetString("DefaultFolderName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connected.
/// </summary>
internal static string FlightSimulatorConnected {
get {
return ResourceManager.GetString("FlightSimulatorConnected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connecting....
/// </summary>
internal static string FlightSimulatorConnecting {
get {
return ResourceManager.GetString("FlightSimulatorConnecting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed, will retry in {0} seconds....
/// </summary>
internal static string FlightSimulatorFailed {
get {
return ResourceManager.GetString("FlightSimulatorFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Closed, will retry in {0} seconds....
/// </summary>
internal static string FlightSimulatorQuit {
get {
return ResourceManager.GetString("FlightSimulatorQuit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &amp;Start recording.
/// </summary>
internal static string RecordButtonStart {
get {
return ResourceManager.GetString("RecordButtonStart", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &amp;Stop recording.
/// </summary>
internal static string RecordButtonStop {
get {
return ResourceManager.GetString("RecordButtonStop", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DefaultFolderName" xml:space="preserve">
<value>Flight logs</value>
</data>
<data name="FlightSimulatorConnected" xml:space="preserve">
<value>Connected</value>
</data>
<data name="FlightSimulatorConnecting" xml:space="preserve">
<value>Connecting...</value>
</data>
<data name="FlightSimulatorFailed" xml:space="preserve">
<value>Failed, will retry in {0} seconds...</value>
</data>
<data name="FlightSimulatorQuit" xml:space="preserve">
<value>Closed, will retry in {0} seconds...</value>
</data>
<data name="RecordButtonStart" xml:space="preserve">
<value>&amp;Start recording</value>
</data>
<data name="RecordButtonStop" xml:space="preserve">
<value>&amp;Stop recording</value>
</data>
</root>

View File

@ -0,0 +1,20 @@
namespace FSFlightLogger
{
public class Settings
{
public int? MainFormLeft { get; set; }
public int? MainFormTop { get; set; }
public bool CSVEnabled { get; set; }
public string CSVPath { get; set; }
public bool KMLEnabled { get; set; }
public string KMLPath { get; set; }
public bool KMLLiveEnabled { get; set; }
public int KMLLivePort { get; set; }
public bool TriggerConnected { get; set; }
public bool TriggerWaitForMovement { get; set; }
public bool TriggerNewLogStationaryEnabled { get; set; }
public int TriggerNewLogStationarySeconds { get; set; }
}
}

View File

@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FlightLoggerLib;
using FlightLoggerLib.Concrete;
using Nito.AsyncEx;
using SimConnect;
using SimConnect.Attribute;
using SimConnect.Lib;
namespace FSFlightLogger
{
public class SimConnectLogger : IAsyncDisposable
{
private readonly ISimConnectClient client;
private SimConnectLoggerWorker worker;
public class Config
{
public string CSVOutputPath { get; set; }
public string KMLOutputPath { get; set; }
public int? KMLLivePort { get; set; }
public TimeSpan IntervalTime { get; set; }
public int IntervalDistance { get; set; }
public bool WaitForMovement { get; set; }
public TimeSpan? NewLogWhenIdleSeconds { get; set; }
}
public SimConnectLogger(ISimConnectClient client)
{
this.client = client;
}
public async ValueTask DisposeAsync()
{
// Do not dispose of client, it is managed by the caller
await Stop();
}
public async Task Start(Config config)
{
await Stop();
worker = new SimConnectLoggerWorker(client, config);
}
public async Task Stop()
{
if (worker != null)
{
await worker.DisposeAsync();
worker = null;
}
}
private class SimConnectLoggerWorker : IAsyncDisposable
{
private readonly Config config;
private readonly List<IFlightLogger> loggers = new List<IFlightLogger>();
private IAsyncDisposable definition;
private IAsyncDisposable simEvent;
private IAsyncDisposable pauseEvent;
private DateTime lastTime;
private PositionData lastData = PositionData.Empty();
private bool waitingForMovement;
private DateTime lastStopped;
private readonly AsyncLock simStateLock = new AsyncLock();
private volatile bool paused = true;
private volatile bool running = false;
public SimConnectLoggerWorker(ISimConnectClient client, Config config)
{
this.config = config;
if (!string.IsNullOrEmpty(config.CSVOutputPath))
{
Directory.CreateDirectory(config.CSVOutputPath);
loggers.Add(new CSVFlightLogger(config.CSVOutputPath));
}
if (!string.IsNullOrEmpty(config.KMLOutputPath))
{
Directory.CreateDirectory(config.KMLOutputPath);
loggers.Add(new KMLFlightLogger(config.KMLOutputPath, TimeSpan.FromSeconds(5)));
}
if (config.KMLLivePort.HasValue)
loggers.Add(new KMLLiveFlightLogger(config.KMLLivePort.Value));
waitingForMovement = config.WaitForMovement;
lastStopped = DateTime.Now;
pauseEvent = client.SubscribeToSystemEvent(SimConnectSystemEvent.Pause, HandlePauseEvent);
simEvent = client.SubscribeToSystemEvent(SimConnectSystemEvent.Sim, HandleSimEvent);
definition = client.AddDefinition<PositionData>(HandlePositionData);
}
public async ValueTask DisposeAsync()
{
if (definition != null)
{
await definition.DisposeAsync();
definition = null;
}
if (pauseEvent != null)
{
await pauseEvent.DisposeAsync();
pauseEvent = null;
}
if (simEvent != null)
{
await simEvent.DisposeAsync();
simEvent = null;
}
foreach (var logger in loggers)
await logger.DisposeAsync();
}
private async Task HandlePauseEvent(SimConnectSystemEventArgs args)
{
using (await simStateLock.LockAsync())
{
paused = ((SimConnectPauseSystemEventArgs)args).Paused;
}
}
private async Task HandleSimEvent(SimConnectSystemEventArgs args)
{
using (await simStateLock.LockAsync())
{
running = ((SimConnectSimSystemEventArgs)args).SimRunning;
}
}
private async Task HandlePositionData(PositionData data)
{
if (paused || !running)
return;
var moving = data.Airspeed > 0;
if (!moving && waitingForMovement)
return;
waitingForMovement = false;
// TODO take vertical position into account when going straight up or down (not a common use case, but still)
var distanceMeters = LatLon.DistanceBetweenInMeters(lastData.Latitude, lastData.Longitude, data.Latitude, data.Longitude);
if (distanceMeters < config.IntervalDistance)
{
if (data.Airspeed < 0.1)
data.Airspeed = 0;
// Make an exception if we were last moving and have now stopped, so the 0 velocity record is logged as well
if (data.Airspeed > 0 || lastData.Airspeed == 0)
return;
}
var now = DateTime.Now;
if (config.NewLogWhenIdleSeconds.HasValue)
{
if (moving)
{
if (now - lastStopped >= config.NewLogWhenIdleSeconds)
{
await Task.WhenAll(loggers.Select(logger =>
logger.NewLog()));
}
}
lastStopped = now;
}
var time = now - lastTime;
if (time < config.IntervalTime)
return;
lastTime = now;
lastData = data;
// ReSharper disable once AccessToDisposedClosure - covered by disposing the client first
await Task.WhenAll(loggers.Select(logger =>
logger.LogPosition(now, new FlightPosition
{
Latitude = data.Latitude,
Longitude = data.Longitude,
Altitude = data.Altitude,
Airspeed = data.Airspeed
})));
}
}
// ReSharper disable once ClassNeverInstantiated.Local - it is, by the SimConnect client
public class PositionData
{
[SimConnectVariable("PLANE LATITUDE", "degrees")]
public float Latitude;
[SimConnectVariable("PLANE LONGITUDE", "degrees")]
public float Longitude;
[SimConnectVariable("PLANE ALTITUDE", "feet")]
public float Altitude;
[SimConnectVariable("AIRSPEED INDICATED", "knots")]
public float Airspeed;
public static PositionData Empty()
{
return new PositionData
{
Latitude = 0,
Longitude = 0,
Altitude = 0,
Airspeed = 0
};
}
}
}
}

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FSFlightLogger {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class en_US {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal en_US() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FSFlightLogger.en-US", typeof(en_US).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,7 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.0.0" targetFramework="net472" /> <package id="Microsoft.Bcl.AsyncInterfaces" version="1.0.0" targetFramework="net472" />
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net472" />
<package id="Nito.AsyncEx.Context" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Coordination" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Interop.WaitHandles" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Oop" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Tasks" version="5.0.0" targetFramework="net472" />
<package id="Nito.Cancellation" version="1.0.5" targetFramework="net472" />
<package id="Nito.Collections.Deque" version="1.0.4" targetFramework="net472" />
<package id="Nito.Disposables" version="2.0.0" targetFramework="net472" />
<package id="SimpleInjector" version="5.0.3" targetFramework="net472" /> <package id="SimpleInjector" version="5.0.3" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" /> <package id="System.Collections.Immutable" version="1.4.0" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.2" targetFramework="net472" /> <package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
</packages> </packages>

View File

@ -1,6 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup> </startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration> </configuration>

View File

@ -55,8 +55,17 @@
<Reference Include="CommandLine, Version=2.8.0.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL"> <Reference Include="CommandLine, Version=2.8.0.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL">
<HintPath>..\packages\CommandLineParser.2.8.0\lib\net461\CommandLine.dll</HintPath> <HintPath>..\packages\CommandLineParser.2.8.0\lib\net461\CommandLine.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommandLine; using CommandLine;
@ -15,6 +16,7 @@ using SimConnect.Lib;
namespace FSFlightLoggerCmd namespace FSFlightLoggerCmd
{ {
// TODO verb for converting CSV to KML // TODO verb for converting CSV to KML
// TODO with the introduction of the GUI and SimConnectLogger, either port to SimConnectLogger for a single code base or remove this project
public class PositionData public class PositionData
@ -115,7 +117,7 @@ namespace FSFlightLoggerCmd
}; };
client.AddDefinition<PositionData>(data => client.AddDefinition<PositionData>(async data =>
{ {
// TODO take vertical position into account when going straight up or down (not a common use case, but still) // TODO take vertical position into account when going straight up or down (not a common use case, but still)
var distanceMeters = LatLon.DistanceBetweenInMeters(lastData.Latitude, lastData.Longitude, data.Latitude, data.Longitude); var distanceMeters = LatLon.DistanceBetweenInMeters(lastData.Latitude, lastData.Longitude, data.Latitude, data.Longitude);
@ -140,14 +142,14 @@ namespace FSFlightLoggerCmd
lastData = data; lastData = data;
// ReSharper disable once AccessToDisposedClosure - covered by disposing the client first // ReSharper disable once AccessToDisposedClosure - covered by disposing the client first
loggers.ForEach(logger => await Task.WhenAll(loggers.Select(logger =>
logger.LogPosition(now, new FlightPosition logger.LogPosition(now, new FlightPosition
{ {
Latitude = data.Latitude, Latitude = data.Latitude,
Longitude = data.Longitude, Longitude = data.Longitude,
Altitude = data.Altitude, Altitude = data.Altitude,
Airspeed = data.Airspeed Airspeed = data.Airspeed
})); })));
}); });
@ -163,8 +165,8 @@ namespace FSFlightLoggerCmd
stopEvent.Wait(Timeout.Infinite); stopEvent.Wait(Timeout.Infinite);
Console.WriteLine("Closing..."); Console.WriteLine("Closing...");
client.Dispose(); client.DisposeAsync();
loggers.ForEach(logger => logger.Dispose()); loggers.ForEach(logger => logger.DisposeAsync());
if (!Debugger.IsAttached) if (!Debugger.IsAttached)
return; return;

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="CommandLineParser" version="2.8.0" targetFramework="net472" /> <package id="CommandLineParser" version="2.8.0" targetFramework="net472" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.1.1" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
</packages> </packages>

View File

@ -11,25 +11,30 @@ namespace FlightLoggerLib.Concrete
{ {
public class CSVFlightLogger : IFlightLogger public class CSVFlightLogger : IFlightLogger
{ {
private readonly CsvWriter output; private readonly string path;
private CsvWriter output;
public CSVFlightLogger(string path) public CSVFlightLogger(string path)
{ {
var filename = Path.Combine(path, DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss") + ".csv"); this.path = path;
var header = !File.Exists(filename);
output = new CsvWriter(new StreamWriter(filename, true), new CsvConfiguration(CultureInfo.CurrentCulture)
{
SanitizeForInjection = false,
HasHeaderRecord = header
});
} }
public void Dispose() public async ValueTask DisposeAsync()
{ {
output?.Dispose(); if (output != null)
await output.DisposeAsync();
}
public async Task NewLog()
{
if (output != null)
{
await output.DisposeAsync();
output = null;
}
} }
@ -44,6 +49,18 @@ namespace FlightLoggerLib.Concrete
Airspeed = position.Airspeed Airspeed = position.Airspeed
}; };
if (output == null)
{
var filename = Path.Combine(path, eventTime.ToString("yyyy-MM-dd HH.mm.ss") + ".csv");
var header = !File.Exists(filename);
output = new CsvWriter(new StreamWriter(filename, true), new CsvConfiguration(CultureInfo.CurrentCulture)
{
SanitizeForInjection = false,
HasHeaderRecord = header
});
}
await output.WriteRecordsAsync(Enumerable.Repeat(record, 1)); await output.WriteRecordsAsync(Enumerable.Repeat(record, 1));
await output.FlushAsync(); await output.FlushAsync();
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharpKml.Base; using SharpKml.Base;
using SharpKml.Dom; using SharpKml.Dom;
@ -9,11 +8,12 @@ namespace FlightLoggerLib.Concrete
{ {
public class KMLFlightLogger : IFlightLogger public class KMLFlightLogger : IFlightLogger
{ {
private readonly string filename; private readonly string path;
private string filename;
private readonly System.TimeSpan flushInterval; private readonly System.TimeSpan flushInterval;
private readonly Document output; private Document output;
private readonly Folder rootFolder; private Folder rootFolder;
private readonly LineString positionPath; private LineString positionPath;
private DateTime lastFlush = DateTime.MinValue; private DateTime lastFlush = DateTime.MinValue;
private Vector lastPosition; private Vector lastPosition;
@ -23,11 +23,74 @@ namespace FlightLoggerLib.Concrete
public KMLFlightLogger(string path, System.TimeSpan flushInterval) public KMLFlightLogger(string path, System.TimeSpan flushInterval)
{ {
var dateString = DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss"); this.path = path;
filename = Path.Combine(path, dateString + ".kml");
this.flushInterval = flushInterval; this.flushInterval = flushInterval;
}
public async Task NewLog()
{
if (output == null)
return;
CheckEndPoint();
await Flush();
output = null;
}
public async ValueTask DisposeAsync()
{
if (output == null)
return;
CheckEndPoint();
await Flush();
}
protected void CheckEndPoint()
{
// TODO replace last "full stop" point if they match
if (lastPosition != null)
{
AddPoint(lastPosition, "End", "end");
lastPosition = null;
}
}
protected async Task AutoFlush()
{
var now = DateTime.Now;
var diff = now - lastFlush;
if (diff < flushInterval)
return;
await Flush();
lastFlush = now;
}
protected async Task Flush()
{
var serializer = new Serializer();
serializer.Serialize(output);
using (var writer = new StreamWriter(filename))
{
await writer.WriteAsync(serializer.Xml);
}
}
protected void EnsureOutput(DateTime eventTime)
{
if (output != null)
return;
var dateString = eventTime.ToString("yyyy-MM-dd HH.mm.ss");
filename = Path.Combine(path, dateString + ".kml");
// Create folder // Create folder
rootFolder = new Folder rootFolder = new Folder
{ {
@ -65,38 +128,6 @@ namespace FlightLoggerLib.Concrete
output.AddFeature(rootFolder); output.AddFeature(rootFolder);
} }
public void Dispose()
{
if (lastPosition != null)
AddPoint(lastPosition, "End", "end");
Flush().Wait();
}
protected async Task AutoFlush()
{
var now = DateTime.Now;
var diff = now - lastFlush;
if (diff < flushInterval)
return;
await Flush();
lastFlush = now;
}
protected async Task Flush()
{
var serializer = new Serializer();
serializer.Serialize(output);
using (var writer = new StreamWriter(filename))
{
await writer.WriteAsync(serializer.Xml);
}
}
protected void AddFlightPathStyleMap() protected void AddFlightPathStyleMap()
{ {
@ -185,6 +216,8 @@ namespace FlightLoggerLib.Concrete
public async Task LogPosition(DateTime eventTime, FlightPosition position) public async Task LogPosition(DateTime eventTime, FlightPosition position)
{ {
EnsureOutput(eventTime);
var altitudeMeters = position.Altitude * MetersPerFoot; var altitudeMeters = position.Altitude * MetersPerFoot;
var coordinate = new Vector(position.Latitude, position.Longitude, altitudeMeters); var coordinate = new Vector(position.Latitude, position.Longitude, altitudeMeters);

View File

@ -0,0 +1,164 @@
using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Ionic.BZip2;
using Nito.AsyncEx;
using SharpKml.Base;
using SharpKml.Dom;
namespace FlightLoggerLib.Concrete
{
public class KMLLiveFlightLogger : IFlightLogger
{
private readonly CancellationTokenSource listenerCancellationTokenSource = new CancellationTokenSource();
private readonly HttpListener listener;
private readonly Task listenerTask;
private readonly string baseUrl;
private FlightPosition currentPosition;
private readonly AsyncLock currentPositionLock = new AsyncLock();
private byte[] entryDocument;
private Document dynamicDocument;
private Placemark dynamicPlacemark;
public KMLLiveFlightLogger(int port)
{
baseUrl = $"http://127.0.0.1:{port}/";
PrepareEntryDocument();
listener = new HttpListener();
listener.Prefixes.Add(baseUrl);
listener.Start();
listenerTask = Task.Run(RunServer);
}
public async ValueTask DisposeAsync()
{
listener?.Stop();
listenerCancellationTokenSource.Cancel();
await listenerTask;
}
protected void PrepareEntryDocument()
{
var networkLink = new NetworkLink
{
Name = "Refreshes every 5 seconds",
Link = new Link
{
Href = new Uri($"{baseUrl}live/dynamic", UriKind.Absolute),
RefreshMode = RefreshMode.OnInterval,
RefreshInterval = 5
}
};
var output = new Document();
output.AddFeature(networkLink);
var serializer = new Serializer();
serializer.Serialize(output);
entryDocument = Encoding.UTF8.GetBytes(serializer.Xml);
}
protected void PrepareDynamicDocument(Vector coordinate, float altitudeInFeet)
{
var name = $"Live location ({altitudeInFeet:#} feet)";
if (dynamicPlacemark != null)
{
dynamicPlacemark.Name = name;
((Point)dynamicPlacemark.Geometry).Coordinate = coordinate;
return;
}
var point = new Point { Coordinate = coordinate, AltitudeMode = AltitudeMode.Absolute };
dynamicPlacemark = new Placemark
{
Name = name,
//StyleUrl = new Uri("#" + styleMapId, UriKind.Relative),
Geometry = point,
Visibility = true
};
dynamicDocument = new Document();
dynamicDocument.AddFeature(dynamicPlacemark);
}
public Task NewLog()
{
return Task.CompletedTask;
}
public async Task LogPosition(DateTime eventTime, FlightPosition position)
{
using (await currentPositionLock.LockAsync())
{
currentPosition = position;
}
}
private const float MetersPerFoot = 0.3048f;
private async Task RunServer()
{
while (!listenerCancellationTokenSource.IsCancellationRequested)
{
var context = await listener.GetContextAsync();
switch (context.Request.Url.AbsolutePath)
{
case "/live":
context.Response.StatusCode = 200;
await context.Response.OutputStream.WriteAsync(entryDocument, 0, entryDocument.Length);
break;
case "/live/dynamic":
{
byte[] buffer;
using (await currentPositionLock.LockAsync())
{
var altitudeFeet = currentPosition?.Altitude ?? -10;
var altitudeMeters = altitudeFeet * MetersPerFoot;
PrepareDynamicDocument(new Vector
{
Latitude = currentPosition?.Latitude ?? 24.999979,
Longitude = currentPosition?.Longitude ?? -70.999997,
Altitude = altitudeMeters
},
altitudeFeet);
var serializer = new Serializer();
serializer.Serialize(dynamicDocument);
buffer = Encoding.UTF8.GetBytes(serializer.Xml);
}
context.Response.StatusCode = 200;
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
break;
}
default:
context.Response.StatusCode = 404;
break;
}
context.Response.Close();
}
}
}
}

View File

@ -61,10 +61,37 @@
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath> <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference> </Reference>
<Reference Include="Nito.AsyncEx.Context, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Context.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Context.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Coordination, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Coordination.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Coordination.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Interop.WaitHandles, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Interop.WaitHandles.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Interop.WaitHandles.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Oop, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Oop.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Oop.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Tasks, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Tasks.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Tasks.dll</HintPath>
</Reference>
<Reference Include="Nito.Cancellation, Version=1.0.5.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Cancellation.1.0.5\lib\netstandard2.0\Nito.Cancellation.dll</HintPath>
</Reference>
<Reference Include="Nito.Collections.Deque, Version=1.0.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Collections.Deque.1.0.4\lib\netstandard2.0\Nito.Collections.Deque.dll</HintPath>
</Reference>
<Reference Include="Nito.Disposables, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Disposables.2.0.0\lib\netstandard2.0\Nito.Disposables.dll</HintPath>
</Reference>
<Reference Include="SharpKml.Core, Version=5.1.3.0, Culture=neutral, PublicKeyToken=9bb853c026a5c0ac, processorArchitecture=MSIL"> <Reference Include="SharpKml.Core, Version=5.1.3.0, Culture=neutral, PublicKeyToken=9bb853c026a5c0ac, processorArchitecture=MSIL">
<HintPath>..\packages\SharpKml.Core.5.1.3\lib\net45\SharpKml.Core.dll</HintPath> <HintPath>..\packages\SharpKml.Core.5.1.3\lib\net45\SharpKml.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.IO.Compression" /> <Reference Include="System.IO.Compression" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
@ -83,6 +110,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Concrete\KMLFlightLogger.cs" /> <Compile Include="Concrete\KMLFlightLogger.cs" />
<Compile Include="Concrete\CSVFlightLogger.cs" /> <Compile Include="Concrete\CSVFlightLogger.cs" />
<Compile Include="Concrete\KMLLiveFlightLogger.cs" />
<Compile Include="IFlightLogger.cs" /> <Compile Include="IFlightLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

View File

@ -13,8 +13,9 @@ namespace FlightLoggerLib
public interface IFlightLogger : IDisposable public interface IFlightLogger : IAsyncDisposable
{ {
Task NewLog();
Task LogPosition(DateTime eventTime, FlightPosition position); Task LogPosition(DateTime eventTime, FlightPosition position);
//void LogEvent //void LogEvent
} }

View File

@ -4,7 +4,17 @@
<package id="DotNetZip.Reduced" version="1.9.1.8" targetFramework="net472" /> <package id="DotNetZip.Reduced" version="1.9.1.8" targetFramework="net472" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.1.1" targetFramework="net472" /> <package id="Microsoft.Bcl.AsyncInterfaces" version="1.1.1" targetFramework="net472" />
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net472" /> <package id="Microsoft.CSharp" version="4.7.0" targetFramework="net472" />
<package id="Nito.AsyncEx" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Context" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Coordination" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Interop.WaitHandles" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Oop" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Tasks" version="5.0.0" targetFramework="net472" />
<package id="Nito.Cancellation" version="1.0.5" targetFramework="net472" />
<package id="Nito.Collections.Deque" version="1.0.4" targetFramework="net472" />
<package id="Nito.Disposables" version="2.0.0" targetFramework="net472" />
<package id="SharpKml.Core" version="5.1.3" targetFramework="net472" /> <package id="SharpKml.Core" version="5.1.3" targetFramework="net472" />
<package id="System.Collections.Immutable" version="1.4.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" /> <package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" /> <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net472" /> <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net472" />

View File

@ -5,7 +5,6 @@ using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace SimConnect.Concrete namespace SimConnect.Concrete
{ {
@ -14,12 +13,16 @@ namespace SimConnect.Concrete
/// Requires the following DLL files to be present: FSX2020-SimConnect.dll, FSX-SE-SimConnect.dll, FSX-SE-SimConnect.dll, FSX-SimConnect.dll. /// Requires the following DLL files to be present: FSX2020-SimConnect.dll, FSX-SE-SimConnect.dll, FSX-SE-SimConnect.dll, FSX-SimConnect.dll.
/// These are renamed versions of the SimConnect.dll from the various flight simulator installations. /// These are renamed versions of the SimConnect.dll from the various flight simulator installations.
/// </summary> /// </summary>
public class DefaultSimConnectClient : ISimConnectClient, IDisposable public class DefaultSimConnectClient : ISimConnectClient
{ {
private readonly ISimConnectLibrary simConnectLibrary; private readonly ISimConnectLibrary simConnectLibrary;
private SimConnectWorker worker; private SimConnectWorker worker;
private uint nextDefinitionID = 1; private uint nextDefinitionID = 1;
private uint nextEventID = 1;
private readonly object observersLock = new object();
private readonly List<ISimConnectClientObserver> observers = new List<ISimConnectClientObserver>();
/// <summary> /// <summary>
@ -33,10 +36,12 @@ namespace SimConnect.Concrete
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public async ValueTask DisposeAsync()
{ {
worker?.Close().Wait(); worker?.Close();
simConnectLibrary?.Dispose();
if (simConnectLibrary != null)
await simConnectLibrary.DisposeAsync();
} }
/// <summary> /// <summary>
@ -46,9 +51,7 @@ namespace SimConnect.Concrete
/// <returns></returns> /// <returns></returns>
public async Task<bool> TryOpen(string appName) public async Task<bool> TryOpen(string appName)
{ {
if (worker != null) worker?.Close();
await worker.Close();
worker = new SimConnectWorker(simConnectLibrary, appName); worker = new SimConnectWorker(simConnectLibrary, appName);
return await worker.Open(); return await worker.Open();
} }
@ -57,12 +60,20 @@ namespace SimConnect.Concrete
/// <inheritdoc /> /// <inheritdoc />
public void AttachObserver(ISimConnectClientObserver observer) public void AttachObserver(ISimConnectClientObserver observer)
{ {
throw new NotImplementedException(); Monitor.Enter(observersLock);
try
{
observers.Add(observer);
}
finally
{
Monitor.Exit(observersLock);
}
} }
/// <inheritdoc /> /// <inheritdoc />
public IDisposable AddDefinition<T>(SimConnectDataHandlerAction<T> onData) where T : class public IAsyncDisposable AddDefinition<T>(SimConnectDataHandlerAction<T> onData) where T : class
{ {
if (worker == null) if (worker == null)
throw new InvalidOperationException("TryOpen must be called first"); throw new InvalidOperationException("TryOpen must be called first");
@ -81,7 +92,41 @@ namespace SimConnect.Concrete
} }
private class SimConnectDefinitionRegistration<T> : IDisposable where T : class /// <inheritdoc />
public IAsyncDisposable SubscribeToSystemEvent(SimConnectSystemEvent systemEvent, SimConnectSystemEventAction onEvent)
{
if (worker == null)
throw new InvalidOperationException("TryOpen must be called first");
void HandleData(SimConnectRecvEvent recvEvent)
{
SimConnectSystemEventArgs args;
switch (systemEvent)
{
case SimConnectSystemEvent.Pause:
args = new SimConnectPauseSystemEventArgs { Paused = recvEvent.dwData == 1 };
break;
case SimConnectSystemEvent.Sim:
args = new SimConnectSimSystemEventArgs { SimRunning = recvEvent.dwData == 1 };
break;
default:
throw new ArgumentOutOfRangeException(nameof(systemEvent), systemEvent, null);
}
onEvent(args);
}
var eventRegistration = new SimConnectSystemEventRegistration(nextEventID, systemEvent, HandleData, worker);
nextEventID++;
return eventRegistration;
}
private class SimConnectDefinitionRegistration<T> : IAsyncDisposable where T : class
{ {
private readonly uint definitionID; private readonly uint definitionID;
private readonly SimConnectWorker worker; private readonly SimConnectWorker worker;
@ -96,9 +141,33 @@ namespace SimConnect.Concrete
} }
public void Dispose() public ValueTask DisposeAsync()
{ {
worker.UnregisterDefinition(definitionID); worker.UnregisterDefinition(definitionID);
return default;
}
}
private class SimConnectSystemEventRegistration : IAsyncDisposable
{
private readonly uint eventID;
private readonly SimConnectWorker worker;
public SimConnectSystemEventRegistration(uint eventID, SimConnectSystemEvent systemEvent, Action<SimConnectRecvEvent> onData, SimConnectWorker worker)
{
this.eventID = eventID;
this.worker = worker;
worker.SubscribeToSystemEvent(eventID, systemEvent, onData);
}
public ValueTask DisposeAsync()
{
worker.UnsubscribeFromSystemEvent(eventID);
return default;
} }
} }
@ -117,6 +186,7 @@ namespace SimConnect.Concrete
private readonly TaskCompletionSource<bool> openResult = new TaskCompletionSource<bool>(); private readonly TaskCompletionSource<bool> openResult = new TaskCompletionSource<bool>();
private readonly ConcurrentDictionary<uint, Action<Stream>> definitionDataHandler = new ConcurrentDictionary<uint, Action<Stream>>(); private readonly ConcurrentDictionary<uint, Action<Stream>> definitionDataHandler = new ConcurrentDictionary<uint, Action<Stream>>();
private readonly ConcurrentDictionary<uint, Action<SimConnectRecvEvent>> eventDataHandler = new ConcurrentDictionary<uint, Action<SimConnectRecvEvent>>();
public SimConnectWorker(ISimConnectLibrary simConnectLibrary, string appName) public SimConnectWorker(ISimConnectLibrary simConnectLibrary, string appName)
@ -135,12 +205,10 @@ namespace SimConnect.Concrete
} }
public async Task Close() public void Close()
{ {
closed = true; closed = true;
workerPulse.Set(); workerPulse.Set();
await workerTask;
} }
@ -170,6 +238,29 @@ namespace SimConnect.Concrete
} }
public void SubscribeToSystemEvent(uint eventID, SimConnectSystemEvent systemEvent, Action<SimConnectRecvEvent> onData)
{
Enqueue(hSimConnect =>
{
eventDataHandler.AddOrUpdate(eventID, onData, (key, value) => onData);
var result = simConnectLibrary.SimConnect_SubscribeToSystemEvent(hSimConnect, eventID, systemEvent.ToString());
if (result == 0)
return;
});
}
public void UnsubscribeFromSystemEvent(uint eventID)
{
Enqueue(hSimConnect =>
{
eventDataHandler.TryRemove(eventID, out var unused);
simConnectLibrary.SimConnect_UnsubscribeFromSystemEvent(hSimConnect, eventID);
});
}
private void Enqueue(Action<IntPtr> work) private void Enqueue(Action<IntPtr> work)
{ {
lock(workerLock) lock(workerLock)
@ -241,21 +332,30 @@ namespace SimConnect.Concrete
{ {
case SimConnectRecvID.Exception: case SimConnectRecvID.Exception:
var recvException = Marshal.PtrToStructure<SimConnectRecvException>(dataPtr); var recvException = Marshal.PtrToStructure<SimConnectRecvException>(dataPtr);
// TODO provide a way to get insight into exceptions
if (recvException.dwException == 0) if (recvException.dwException == 0)
break; break;
break; break;
case SimConnectRecvID.Event:
var recvEvent = Marshal.PtrToStructure<SimConnectRecvEvent>(dataPtr);
if (!eventDataHandler.TryGetValue(recvEvent.uEventID, out var eventHandler))
break;
eventHandler(recvEvent);
break;
case SimConnectRecvID.SimobjectData: case SimConnectRecvID.SimobjectData:
case SimConnectRecvID.SimobjectDataByType: case SimConnectRecvID.SimobjectDataByType:
var recvSimobjectData = Marshal.PtrToStructure<SimConnectRecvSimobjectData>(dataPtr); var recvSimobjectData = Marshal.PtrToStructure<SimConnectRecvSimobjectData>(dataPtr);
if (!definitionDataHandler.TryGetValue((uint)recvSimobjectData.dwDefineID, out var dataHandler)) if (!definitionDataHandler.TryGetValue(recvSimobjectData.dwDefineID, out var dataHandler))
break; break;
unsafe unsafe
{ {
var streamOffset = Marshal.OffsetOf<SimConnectRecvSimobjectData>("dwData").ToInt32(); var streamOffset = Marshal.OffsetOf<SimConnectRecvSimobjectData>("dwData").ToInt32();
var stream = new UnmanagedMemoryStream((byte*)IntPtr.Add(dataPtr, streamOffset).ToPointer(), (long)dataSize - streamOffset); var stream = new UnmanagedMemoryStream((byte*)IntPtr.Add(dataPtr, streamOffset).ToPointer(), dataSize - streamOffset);
dataHandler(stream); dataHandler(stream);
} }
break; break;

View File

@ -1,13 +1,50 @@
using System; using System;
using System.Threading.Tasks;
namespace SimConnect namespace SimConnect
{ {
public enum SimConnectSystemEvent
{
/// <summary>
/// Request notifications when the flight is paused or unpaused. args will be of type SimConnectPauseSystemEventArgs.
/// </summary>
Pause,
/// <summary>
/// Request notifications when the flight is running or not. args will be of type SimConnectSimSystemEventArgs.
/// </summary>
Sim
}
public abstract class SimConnectSystemEventArgs
{
}
public class SimConnectPauseSystemEventArgs : SimConnectSystemEventArgs
{
public bool Paused { get; set; }
}
public class SimConnectSimSystemEventArgs : SimConnectSystemEventArgs
{
public bool SimRunning { get; set; }
}
public delegate Task SimConnectSystemEventAction(SimConnectSystemEventArgs args);
/// <summary> /// <summary>
/// Called when new data arrives from the SimConnect server. /// Called when new data arrives from the SimConnect server.
/// </summary> /// </summary>
/// <param name="data">An instance of the data class as passed to AddDefinition containing the variable values</param> /// <param name="data">An instance of the data class as passed to AddDefinition containing the variable values</param>
/// <typeparam name="T">The data class as passed to AddDefinition</typeparam> /// <typeparam name="T">The data class as passed to AddDefinition</typeparam>
public delegate void SimConnectDataHandlerAction<in T>(T data) where T : class; public delegate Task SimConnectDataHandlerAction<in T>(T data) where T : class;
/// <summary> /// <summary>
@ -26,7 +63,7 @@ namespace SimConnect
/// <summary> /// <summary>
/// Provides access to the SimConnect library. /// Provides access to the SimConnect library.
/// </summary> /// </summary>
public interface ISimConnectClient : IDisposable public interface ISimConnectClient : IAsyncDisposable
{ {
/// <summary> /// <summary>
/// Attaches the specified observer to receive status notifications. /// Attaches the specified observer to receive status notifications.
@ -40,7 +77,16 @@ namespace SimConnect
/// </summary> /// </summary>
/// <param name="onData">A callback method which is called whenever a data update is received</param> /// <param name="onData">A callback method which is called whenever a data update is received</param>
/// <typeparam name="T">A class defining the variables to monitor annotated using the SimConnectVariable attribute</typeparam> /// <typeparam name="T">A class defining the variables to monitor annotated using the SimConnectVariable attribute</typeparam>
/// <returns>An IDisposable which can be disposed to unregister the definition. Dispose is not required to be called when the client is disconnected, but will not throw an exception.</returns> /// <returns>An IAsyncDisposable which can be disposed to unregister the definition. Dispose is not required to be called when the client is disconnected, but will not throw an exception.</returns>
IDisposable AddDefinition<T>(SimConnectDataHandlerAction<T> onData) where T : class; IAsyncDisposable AddDefinition<T>(SimConnectDataHandlerAction<T> onData) where T : class;
/// <summary>
/// Subscribes to a SimConnect system event.
/// </summary>
/// <param name="systemEvent">The type of event to subscribe to.</param>
/// <param name="onEvent">The callback method called whenever the system event occurs.</param>
/// <returns>An IAsyncDisposable which can be disposed to unregister the definition. Dispose is not required to be called when the client is disconnected, but will not throw an exception.</returns>
IAsyncDisposable SubscribeToSystemEvent(SimConnectSystemEvent systemEvent, SimConnectSystemEventAction onEvent);
} }
} }

View File

@ -128,6 +128,17 @@ namespace SimConnect
} }
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct SimConnectRecvEvent
{
public SimConnectRecv Recv;
public uint uGroupID;
public uint uEventID;
public uint dwData;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct SimConnectRecvSimobjectData public struct SimConnectRecvSimobjectData
{ {
@ -148,7 +159,7 @@ namespace SimConnect
/// <summary> /// <summary>
/// Provides a low-level interface to a compatible SimConnect.dll. /// Provides a low-level interface to a compatible SimConnect.dll.
/// </summary> /// </summary>
public interface ISimConnectLibrary : IDisposable public interface ISimConnectLibrary : IAsyncDisposable
{ {
uint SimConnect_Open(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex); uint SimConnect_Open(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex);
uint SimConnect_Close(IntPtr hSimConnect); uint SimConnect_Close(IntPtr hSimConnect);
@ -158,6 +169,9 @@ namespace SimConnect
uint SimConnect_RequestDataOnSimObject(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0); uint SimConnect_RequestDataOnSimObject(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0);
uint SimConnect_GetNextDispatch(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData); uint SimConnect_GetNextDispatch(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData);
uint SimConnect_SubscribeToSystemEvent(IntPtr hSimConnect, uint eventID, string systemEventName);
uint SimConnect_UnsubscribeFromSystemEvent(IntPtr hSimConnect, uint eventID);
} }

View File

@ -56,8 +56,17 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -94,5 +103,9 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View File

@ -1,10 +1,11 @@
using System; using System;
using System.Threading.Tasks;
using SimConnect.Lib; using SimConnect.Lib;
#pragma warning disable 1591 #pragma warning disable 1591
namespace SimConnect.Unmanaged namespace SimConnect.Unmanaged
{ {
public class SimConnectDLLLibrary : ISimConnectLibrary, IDisposable public class SimConnectDLLLibrary : ISimConnectLibrary, IAsyncDisposable
{ {
private delegate uint SimConnectOpenProc(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex); private delegate uint SimConnectOpenProc(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex);
private delegate uint SimConnectCloseProc(IntPtr hSimConnect); private delegate uint SimConnectCloseProc(IntPtr hSimConnect);
@ -12,6 +13,9 @@ namespace SimConnect.Unmanaged
private delegate uint SimConnectClearDataDefinitionProc(IntPtr hSimConnect, uint defineID); private delegate uint SimConnectClearDataDefinitionProc(IntPtr hSimConnect, uint defineID);
private delegate uint SimConnectRequestDataOnSimObjectProc(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0); private delegate uint SimConnectRequestDataOnSimObjectProc(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0);
private delegate uint SimConnectGetNextDispatchProc(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData); private delegate uint SimConnectGetNextDispatchProc(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData);
private delegate uint SimConnectSubscribeToSystemEventProc(IntPtr hSimConnect, uint eventID, string systemEventName);
private delegate uint SimConnectUnsubscribeFromSystemEventProc(IntPtr hSimConnect, uint eventID);
private readonly UnmanagedLibrary library; private readonly UnmanagedLibrary library;
@ -21,6 +25,8 @@ namespace SimConnect.Unmanaged
private readonly SimConnectClearDataDefinitionProc simConnectClearDataDefinition; private readonly SimConnectClearDataDefinitionProc simConnectClearDataDefinition;
private readonly SimConnectRequestDataOnSimObjectProc simConnectRequestDataOnSimObject; private readonly SimConnectRequestDataOnSimObjectProc simConnectRequestDataOnSimObject;
private readonly SimConnectGetNextDispatchProc simConnectGetNextDispatch; private readonly SimConnectGetNextDispatchProc simConnectGetNextDispatch;
private readonly SimConnectSubscribeToSystemEventProc simConnectSubscribeToSystemEvent;
private readonly SimConnectUnsubscribeFromSystemEventProc simConnectUnsubscribeFromSystemEvent;
@ -33,11 +39,14 @@ namespace SimConnect.Unmanaged
simConnectClearDataDefinition = library.GetUnmanagedFunction<SimConnectClearDataDefinitionProc>("SimConnect_ClearDataDefinition"); simConnectClearDataDefinition = library.GetUnmanagedFunction<SimConnectClearDataDefinitionProc>("SimConnect_ClearDataDefinition");
simConnectRequestDataOnSimObject = library.GetUnmanagedFunction<SimConnectRequestDataOnSimObjectProc>("SimConnect_RequestDataOnSimObject"); simConnectRequestDataOnSimObject = library.GetUnmanagedFunction<SimConnectRequestDataOnSimObjectProc>("SimConnect_RequestDataOnSimObject");
simConnectGetNextDispatch = library.GetUnmanagedFunction<SimConnectGetNextDispatchProc>("SimConnect_GetNextDispatch"); simConnectGetNextDispatch = library.GetUnmanagedFunction<SimConnectGetNextDispatchProc>("SimConnect_GetNextDispatch");
simConnectSubscribeToSystemEvent = library.GetUnmanagedFunction<SimConnectSubscribeToSystemEventProc>("SimConnect_SubscribeToSystemEvent");
simConnectUnsubscribeFromSystemEvent = library.GetUnmanagedFunction<SimConnectUnsubscribeFromSystemEventProc>("SimConnect_UnsubscribeFromSystemEvent");
} }
public void Dispose() public ValueTask DisposeAsync()
{ {
library?.Dispose(); library?.Dispose();
return default;
} }
@ -72,6 +81,17 @@ namespace SimConnect.Unmanaged
{ {
return simConnectGetNextDispatch(hSimConnect, out ppData, out pcbData); return simConnectGetNextDispatch(hSimConnect, out ppData, out pcbData);
} }
public uint SimConnect_SubscribeToSystemEvent(IntPtr hSimConnect, uint eventID, string systemEventName)
{
return simConnectSubscribeToSystemEvent(hSimConnect, eventID, systemEventName);
}
public uint SimConnect_UnsubscribeFromSystemEvent(IntPtr hSimConnect, uint eventID)
{
return simConnectUnsubscribeFromSystemEvent(hSimConnect, eventID);
}
} }
} }
#pragma warning restore 1591 #pragma warning restore 1591

15
SimConnect/app.config Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.1.1" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
</packages>

View File

@ -65,6 +65,12 @@
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -89,6 +95,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="FluentAssertions" version="5.10.3" targetFramework="net472" /> <package id="FluentAssertions" version="5.10.3" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
<package id="xunit" version="2.4.1" targetFramework="net472" /> <package id="xunit" version="2.4.1" targetFramework="net472" />
<package id="xunit.abstractions" version="2.0.3" targetFramework="net472" /> <package id="xunit.abstractions" version="2.0.3" targetFramework="net472" />
<package id="xunit.analyzers" version="0.10.0" targetFramework="net472" /> <package id="xunit.analyzers" version="0.10.0" targetFramework="net472" />