diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d6a1883
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+DesignTimeBuild
+bin
+obj
+packages
+
+*.suo
+*.user
diff --git a/FSFlightLogger.sln b/FSFlightLogger.sln
new file mode 100644
index 0000000..7d295fc
--- /dev/null
+++ b/FSFlightLogger.sln
@@ -0,0 +1,71 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30413.136
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSFlightLogger", "FSFlightLogger\FSFlightLogger.csproj", "{5871FA9B-88A7-4F98-8755-2FC48D4DBF44}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimConnectUnitTests", "SimConnectUnitTests\SimConnectUnitTests.csproj", "{2CD97D07-EF26-4CDA-A090-DF3D698E7F60}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSFlightLoggerCmd", "FSFlightLoggerCmd\FSFlightLoggerCmd.csproj", "{767C7EAA-9230-4DEE-89FA-9699288C831B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlightLoggerLib", "FlightLoggerLib\FlightLoggerLib.csproj", "{D85BCC97-F653-4286-98D9-073A33A55857}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimConnect", "SimConnect\SimConnect.csproj", "{F160BB6A-7620-41E5-A99C-948C208875E4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}.Debug|x64.ActiveCfg = Debug|x64
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}.Debug|x64.Build.0 = Debug|x64
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}.Debug|x86.ActiveCfg = Debug|x86
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}.Debug|x86.Build.0 = Debug|x86
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}.Release|x64.ActiveCfg = Release|x64
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}.Release|x64.Build.0 = Release|x64
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}.Release|x86.ActiveCfg = Release|x86
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}.Release|x86.Build.0 = Release|x86
+ {2CD97D07-EF26-4CDA-A090-DF3D698E7F60}.Debug|x64.ActiveCfg = Debug|x64
+ {2CD97D07-EF26-4CDA-A090-DF3D698E7F60}.Debug|x64.Build.0 = Debug|x64
+ {2CD97D07-EF26-4CDA-A090-DF3D698E7F60}.Debug|x86.ActiveCfg = Debug|x86
+ {2CD97D07-EF26-4CDA-A090-DF3D698E7F60}.Debug|x86.Build.0 = Debug|x86
+ {2CD97D07-EF26-4CDA-A090-DF3D698E7F60}.Release|x64.ActiveCfg = Release|x64
+ {2CD97D07-EF26-4CDA-A090-DF3D698E7F60}.Release|x64.Build.0 = Release|x64
+ {2CD97D07-EF26-4CDA-A090-DF3D698E7F60}.Release|x86.ActiveCfg = Release|x86
+ {2CD97D07-EF26-4CDA-A090-DF3D698E7F60}.Release|x86.Build.0 = Release|x86
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}.Debug|x64.ActiveCfg = Debug|x64
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}.Debug|x64.Build.0 = Debug|x64
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}.Debug|x86.ActiveCfg = Debug|x86
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}.Debug|x86.Build.0 = Debug|x86
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}.Release|x64.ActiveCfg = Release|x64
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}.Release|x64.Build.0 = Release|x64
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}.Release|x86.ActiveCfg = Release|x86
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}.Release|x86.Build.0 = Release|x86
+ {D85BCC97-F653-4286-98D9-073A33A55857}.Debug|x64.ActiveCfg = Debug|x64
+ {D85BCC97-F653-4286-98D9-073A33A55857}.Debug|x64.Build.0 = Debug|x64
+ {D85BCC97-F653-4286-98D9-073A33A55857}.Debug|x86.ActiveCfg = Debug|x86
+ {D85BCC97-F653-4286-98D9-073A33A55857}.Debug|x86.Build.0 = Debug|x86
+ {D85BCC97-F653-4286-98D9-073A33A55857}.Release|x64.ActiveCfg = Release|x64
+ {D85BCC97-F653-4286-98D9-073A33A55857}.Release|x64.Build.0 = Release|x64
+ {D85BCC97-F653-4286-98D9-073A33A55857}.Release|x86.ActiveCfg = Release|x86
+ {D85BCC97-F653-4286-98D9-073A33A55857}.Release|x86.Build.0 = Release|x86
+ {F160BB6A-7620-41E5-A99C-948C208875E4}.Debug|x64.ActiveCfg = Debug|x64
+ {F160BB6A-7620-41E5-A99C-948C208875E4}.Debug|x64.Build.0 = Debug|x64
+ {F160BB6A-7620-41E5-A99C-948C208875E4}.Debug|x86.ActiveCfg = Debug|x86
+ {F160BB6A-7620-41E5-A99C-948C208875E4}.Debug|x86.Build.0 = Debug|x86
+ {F160BB6A-7620-41E5-A99C-948C208875E4}.Release|x64.ActiveCfg = Release|x64
+ {F160BB6A-7620-41E5-A99C-948C208875E4}.Release|x64.Build.0 = Release|x64
+ {F160BB6A-7620-41E5-A99C-948C208875E4}.Release|x86.ActiveCfg = Release|x86
+ {F160BB6A-7620-41E5-A99C-948C208875E4}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {427083A8-FC16-46A5-9DD7-FB805B89A26A}
+ EndGlobalSection
+EndGlobal
diff --git a/FSFlightLogger.sln.DotSettings b/FSFlightLogger.sln.DotSettings
new file mode 100644
index 0000000..f548907
--- /dev/null
+++ b/FSFlightLogger.sln.DotSettings
@@ -0,0 +1,9 @@
+
+ False
+ DLL
+ KML
+ XYZ
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/FSFlightLogger/App.config b/FSFlightLogger/App.config
new file mode 100644
index 0000000..56efbc7
--- /dev/null
+++ b/FSFlightLogger/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FSFlightLogger/FSFlightLogger.csproj b/FSFlightLogger/FSFlightLogger.csproj
new file mode 100644
index 0000000..3d74b4a
--- /dev/null
+++ b/FSFlightLogger/FSFlightLogger.csproj
@@ -0,0 +1,124 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {5871FA9B-88A7-4F98-8755-2FC48D4DBF44}
+ WinExe
+ FSFlightLogger
+ FSFlightLogger
+ v4.7.2
+ 512
+ true
+ true
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ 7.3
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ true
+ bin\x64\Debug\
+ x64
+ 7.3
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ 7.3
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ true
+ bin\x86\Debug\
+ x86
+ 7.3
+ MinimumRecommendedRules.ruleset
+ true
+
+
+
+ ..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll
+
+
+ ..\packages\SimpleInjector.5.0.3\lib\net461\SimpleInjector.dll
+
+
+
+
+ ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll
+
+
+ ..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+ MainForm.cs
+
+
+
+
+ MainForm.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+
+
+
+
+ {f160bb6a-7620-41e5-a99c-948c208875e4}
+ SimConnect
+
+
+
+
\ No newline at end of file
diff --git a/FSFlightLogger/MainForm.Designer.cs b/FSFlightLogger/MainForm.Designer.cs
new file mode 100644
index 0000000..683db9f
--- /dev/null
+++ b/FSFlightLogger/MainForm.Designer.cs
@@ -0,0 +1,313 @@
+namespace FSFlightLogger
+{
+ partial class MainForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.components = new System.ComponentModel.Container();
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
+ this.OutputGroupbox = new System.Windows.Forms.GroupBox();
+ this.StatusImageList = new System.Windows.Forms.ImageList(this.components);
+ this.FlightSimulatorStatusIcon = new System.Windows.Forms.PictureBox();
+ this.FlightSimulatorLabel = new System.Windows.Forms.Label();
+ this.FlightSimulatorStatusLabel = new System.Windows.Forms.Label();
+ this.RecordButton = new System.Windows.Forms.Button();
+ 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.TriggerWaitForMovementCheckbox = 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.OutputGroupbox.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.FlightSimulatorStatusIcon)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.RecordingStatusIcon)).BeginInit();
+ this.TriggersGroupbox.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.TriggerNewLogStationaryTimeEdit)).BeginInit();
+ this.SuspendLayout();
+ //
+ // OutputGroupbox
+ //
+ this.OutputGroupbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.OutputGroupbox.Controls.Add(this.OutputKMLPathBrowseButton);
+ this.OutputGroupbox.Controls.Add(this.OutputKMLPathLabel);
+ this.OutputGroupbox.Controls.Add(this.OutputKMLPathTextbox);
+ this.OutputGroupbox.Controls.Add(this.OutputCSVPathBrowseButton);
+ this.OutputGroupbox.Controls.Add(this.OutputCSVPathLabel);
+ this.OutputGroupbox.Controls.Add(this.OutputCSVPathTextbox);
+ this.OutputGroupbox.Controls.Add(this.OutputKMLCheckbox);
+ this.OutputGroupbox.Controls.Add(this.OutputCSVCheckbox);
+ this.OutputGroupbox.Location = new System.Drawing.Point(12, 59);
+ this.OutputGroupbox.Name = "OutputGroupbox";
+ this.OutputGroupbox.Size = new System.Drawing.Size(482, 136);
+ this.OutputGroupbox.TabIndex = 0;
+ this.OutputGroupbox.TabStop = false;
+ this.OutputGroupbox.Text = " Output ";
+ //
+ // StatusImageList
+ //
+ this.StatusImageList.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("StatusImageList.ImageStream")));
+ this.StatusImageList.TransparentColor = System.Drawing.Color.Transparent;
+ this.StatusImageList.Images.SetKeyName(0, "Idle");
+ this.StatusImageList.Images.SetKeyName(1, "Recording");
+ this.StatusImageList.Images.SetKeyName(2, "FSDisconnected");
+ this.StatusImageList.Images.SetKeyName(3, "FSConnected");
+ //
+ // FlightSimulatorStatusIcon
+ //
+ this.FlightSimulatorStatusIcon.Location = new System.Drawing.Point(12, 12);
+ this.FlightSimulatorStatusIcon.Name = "FlightSimulatorStatusIcon";
+ this.FlightSimulatorStatusIcon.Size = new System.Drawing.Size(32, 32);
+ this.FlightSimulatorStatusIcon.TabIndex = 1;
+ this.FlightSimulatorStatusIcon.TabStop = false;
+ //
+ // FlightSimulatorLabel
+ //
+ this.FlightSimulatorLabel.AutoSize = true;
+ this.FlightSimulatorLabel.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.FlightSimulatorLabel.Location = new System.Drawing.Point(50, 9);
+ this.FlightSimulatorLabel.Name = "FlightSimulatorLabel";
+ this.FlightSimulatorLabel.Size = new System.Drawing.Size(133, 21);
+ this.FlightSimulatorLabel.TabIndex = 2;
+ this.FlightSimulatorLabel.Text = "Flight Simulator";
+ //
+ // FlightSimulatorStatusLabel
+ //
+ this.FlightSimulatorStatusLabel.AutoSize = true;
+ this.FlightSimulatorStatusLabel.Location = new System.Drawing.Point(51, 31);
+ this.FlightSimulatorStatusLabel.Name = "FlightSimulatorStatusLabel";
+ this.FlightSimulatorStatusLabel.Size = new System.Drawing.Size(70, 13);
+ this.FlightSimulatorStatusLabel.TabIndex = 3;
+ this.FlightSimulatorStatusLabel.Text = "Connecting...";
+ //
+ // RecordButton
+ //
+ this.RecordButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+ this.RecordButton.Enabled = false;
+ this.RecordButton.Location = new System.Drawing.Point(348, 17);
+ this.RecordButton.Name = "RecordButton";
+ this.RecordButton.Size = new System.Drawing.Size(108, 23);
+ this.RecordButton.TabIndex = 4;
+ this.RecordButton.Text = "&Start recording";
+ this.RecordButton.UseVisualStyleBackColor = true;
+ //
+ // RecordingStatusIcon
+ //
+ this.RecordingStatusIcon.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+ this.RecordingStatusIcon.Location = new System.Drawing.Point(462, 12);
+ this.RecordingStatusIcon.Name = "RecordingStatusIcon";
+ this.RecordingStatusIcon.Size = new System.Drawing.Size(32, 32);
+ this.RecordingStatusIcon.TabIndex = 5;
+ 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
+ //
+ this.TriggersGroupbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryUnitsLabel);
+ this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryTimeEdit);
+ this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryCheckbox);
+ this.TriggersGroupbox.Controls.Add(this.TriggerWaitForMovementCheckbox);
+ this.TriggersGroupbox.Location = new System.Drawing.Point(12, 201);
+ this.TriggersGroupbox.Name = "TriggersGroupbox";
+ this.TriggersGroupbox.Size = new System.Drawing.Size(482, 99);
+ this.TriggersGroupbox.TabIndex = 6;
+ this.TriggersGroupbox.TabStop = false;
+ this.TriggersGroupbox.Text = " Triggers ";
+ //
+ // TriggerWaitForMovementCheckbox
+ //
+ this.TriggerWaitForMovementCheckbox.AutoSize = true;
+ this.TriggerWaitForMovementCheckbox.Checked = true;
+ this.TriggerWaitForMovementCheckbox.CheckState = System.Windows.Forms.CheckState.Checked;
+ this.TriggerWaitForMovementCheckbox.Location = new System.Drawing.Point(10, 23);
+ this.TriggerWaitForMovementCheckbox.Name = "TriggerWaitForMovementCheckbox";
+ this.TriggerWaitForMovementCheckbox.Size = new System.Drawing.Size(454, 17);
+ this.TriggerWaitForMovementCheckbox.TabIndex = 0;
+ this.TriggerWaitForMovementCheckbox.Text = " Wait for movement before logging the starting point (recommended, ignores initia" +
+ "l teleports)";
+ this.TriggerWaitForMovementCheckbox.UseVisualStyleBackColor = true;
+ //
+ // TriggerNewLogStationaryCheckbox
+ //
+ this.TriggerNewLogStationaryCheckbox.AutoSize = true;
+ this.TriggerNewLogStationaryCheckbox.Location = new System.Drawing.Point(10, 46);
+ this.TriggerNewLogStationaryCheckbox.Name = "TriggerNewLogStationaryCheckbox";
+ this.TriggerNewLogStationaryCheckbox.Size = new System.Drawing.Size(353, 17);
+ this.TriggerNewLogStationaryCheckbox.TabIndex = 1;
+ this.TriggerNewLogStationaryCheckbox.Text = " Start a new log when stationary for at least (excluding when paused):";
+ this.TriggerNewLogStationaryCheckbox.UseVisualStyleBackColor = true;
+ //
+ // TriggerNewLogStationaryTimeEdit
+ //
+ this.TriggerNewLogStationaryTimeEdit.Enabled = false;
+ this.TriggerNewLogStationaryTimeEdit.Location = new System.Drawing.Point(33, 69);
+ this.TriggerNewLogStationaryTimeEdit.Name = "TriggerNewLogStationaryTimeEdit";
+ this.TriggerNewLogStationaryTimeEdit.Size = new System.Drawing.Size(78, 20);
+ this.TriggerNewLogStationaryTimeEdit.TabIndex = 2;
+ this.TriggerNewLogStationaryTimeEdit.Value = new decimal(new int[] {
+ 30,
+ 0,
+ 0,
+ 0});
+ //
+ // TriggerNewLogStationaryUnitsLabel
+ //
+ this.TriggerNewLogStationaryUnitsLabel.AutoSize = true;
+ this.TriggerNewLogStationaryUnitsLabel.Location = new System.Drawing.Point(117, 71);
+ this.TriggerNewLogStationaryUnitsLabel.Name = "TriggerNewLogStationaryUnitsLabel";
+ this.TriggerNewLogStationaryUnitsLabel.Size = new System.Drawing.Size(47, 13);
+ this.TriggerNewLogStationaryUnitsLabel.TabIndex = 3;
+ this.TriggerNewLogStationaryUnitsLabel.Text = "seconds";
+ //
+ // MainForm
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(506, 309);
+ this.Controls.Add(this.TriggersGroupbox);
+ this.Controls.Add(this.RecordingStatusIcon);
+ this.Controls.Add(this.RecordButton);
+ this.Controls.Add(this.FlightSimulatorStatusLabel);
+ this.Controls.Add(this.FlightSimulatorLabel);
+ this.Controls.Add(this.FlightSimulatorStatusIcon);
+ this.Controls.Add(this.OutputGroupbox);
+ this.Name = "MainForm";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "FS Flight Logger";
+ this.OutputGroupbox.ResumeLayout(false);
+ this.OutputGroupbox.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.FlightSimulatorStatusIcon)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.RecordingStatusIcon)).EndInit();
+ this.TriggersGroupbox.ResumeLayout(false);
+ this.TriggersGroupbox.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.TriggerNewLogStationaryTimeEdit)).EndInit();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.GroupBox OutputGroupbox;
+ private System.Windows.Forms.Button OutputKMLPathBrowseButton;
+ private System.Windows.Forms.Label OutputKMLPathLabel;
+ private System.Windows.Forms.TextBox OutputKMLPathTextbox;
+ private System.Windows.Forms.Button OutputCSVPathBrowseButton;
+ private System.Windows.Forms.Label OutputCSVPathLabel;
+ private System.Windows.Forms.TextBox OutputCSVPathTextbox;
+ private System.Windows.Forms.CheckBox OutputKMLCheckbox;
+ private System.Windows.Forms.CheckBox OutputCSVCheckbox;
+ private System.Windows.Forms.ImageList StatusImageList;
+ private System.Windows.Forms.PictureBox FlightSimulatorStatusIcon;
+ private System.Windows.Forms.Label FlightSimulatorLabel;
+ private System.Windows.Forms.Label FlightSimulatorStatusLabel;
+ private System.Windows.Forms.Button RecordButton;
+ private System.Windows.Forms.PictureBox RecordingStatusIcon;
+ private System.Windows.Forms.GroupBox TriggersGroupbox;
+ private System.Windows.Forms.Label TriggerNewLogStationaryUnitsLabel;
+ private System.Windows.Forms.NumericUpDown TriggerNewLogStationaryTimeEdit;
+ private System.Windows.Forms.CheckBox TriggerNewLogStationaryCheckbox;
+ private System.Windows.Forms.CheckBox TriggerWaitForMovementCheckbox;
+ }
+}
+
diff --git a/FSFlightLogger/MainForm.cs b/FSFlightLogger/MainForm.cs
new file mode 100644
index 0000000..cfcdd8d
--- /dev/null
+++ b/FSFlightLogger/MainForm.cs
@@ -0,0 +1,49 @@
+using System.Windows.Forms;
+using System.Windows.Forms.VisualStyles;
+using SimConnect;
+
+namespace FSFlightLogger
+{
+ public partial class MainForm : Form
+ {
+ private readonly ISimConnectClientFactory simConnectClientFactory;
+
+
+ public MainForm(ISimConnectClientFactory simConnectClientFactory)
+ {
+ this.simConnectClientFactory = simConnectClientFactory;
+
+ InitializeComponent();
+
+ SetFlightSimulatorConnected(false);
+ SetRecording(false);
+ //var simConnectClient = simConnectClientFactory.TryConnect("FS Flight Logger");
+ }
+
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ components?.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+
+ private void SetFlightSimulatorConnected(bool connected)
+ {
+ FlightSimulatorStatusIcon.Image = StatusImageList.Images[connected ? "FSConnected" : "FSDisconnected"];
+ }
+
+ private void SetRecording(bool recording)
+ {
+ RecordingStatusIcon.Image = StatusImageList.Images[recording ? "Recording" : "Idle"];
+ }
+ }
+}
diff --git a/FSFlightLogger/MainForm.resx b/FSFlightLogger/MainForm.resx
new file mode 100644
index 0000000..a7ddf24
--- /dev/null
+++ b/FSFlightLogger/MainForm.resx
@@ -0,0 +1,474 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+
+ AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
+ LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
+ ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAACO
+ UAAAAk1TRnQBSQFMAgEBBAEAAQgBAAEIAQABIAEAASABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAGA
+ 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/AP8AfgADPAFJA3QBlwOGAcIDiwHZA4sB3AOLAdwDjAHaA4YBwwN0
+ AZgDPQFLAwEBAlQAAjwBOwFJAXMBcgFzAZcBfAF7AYgBwgF1AXQBlQHZAXIBcAGYAdwBcgFwAZgB3AF3
+ AXYBlQHaAXwBewGIAcMCcgFzAZgCPQE8AUsDAQECMAADAQECAwQEBQEHAwUBBwMFAQcDBQEHAwUBBwMF
+ AQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMF
+ AQcDBQEHAwUBBwMFAQcDAwEEAwABARAAAwIBAwMHAQkDCgENAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEO
+ AwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEO
+ AwoBDgMKAQ0DBgEIAwEBAigAAw0BEANzAZYDhgHfA4cB/wOEAf8DhAH/A4sB/wOQAf8DkAH/A4sB/wOE
+ Af8DgwH/A4YB/wOGAeQDdQGbAw8BE0AAAw0BEAJyAXABlgFoAWcBlQHfAUYBRQG0Af8COQG6Af8BMAEx
+ AcMB/wExATMB0QH/ATUBNwHYAf8BNQE3AdgB/wExATMB0QH/AjABwgH/AjgBuQH/AUUBRAGzAf8BZwFm
+ AZYB5AJ0AXEBmwMPARMnAAEBAwYBCAMQARUDGAEiAx4BKwMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMe
+ ASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BKwMX
+ ASADDgETAwQBBg8AAQEDCwEPAxwBKQErAikBRAEyAi8BVgEyAi8BVwEyAi8BVwEyATABLwFXATMBMAEv
+ AVcBMwEwAS8BVwEzATABLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFX
+ ATMCLwFXATMBMAEvAVcBMwEwAS8BVwEzATABLwFXATMBMAEvAVcBMgIvAVcBMgIvAVcBMgEwAS8BVgEp
+ AScBKAFAARsCGgEmAwkBDCQAA14BdgOJAekDeQH/A4YB/wOdAf8DqgH/A7UB/wO6Af8DvQH/A70B/wO5
+ Af8DtAH/A6oB/wOdAf8DhgH/A3cB/wOJAe8DYQF8OAACXwFcAXYCZwGcAekBLgEtAbAB/wEnASgBzwH/
+ AUIBRQHnAf8BVgFcAfAB/wFlAXAB9wH/AW4BfgH6Af8BdAGGAfsB/wFzAYYB+wH/AW0BfAH6Af8BZAFu
+ AfcB/wFVAVwB8AH/AUIBRQHnAf8BJwEoAc8B/wIsAa4B/wJlAZwB7wJiAV4BfCAAAwEBAgMQARUDNgFf
+ A0EBfwNCAYADQgGAA0IBgANCAYADQgGAA0MBgANDAYADQwGAA0MBgANDAYADQwGAA0MBgANDAYADQwGA
+ A0MBgANDAYADQwGAA0IBgANCAYADQgGAA0IBgANCAYADQQF+AzMBVwMLAQ8DAAEBCAADAgEDAR0CHAEp
+ AU0BMQErAb4BWwEbAQoB/QFeARsBCAH/AV4BGwEIAf8BXwEcAQgB/wFhARwBCAH/AWEBGwEIAf8BYwEb
+ AQkB/wFjARsBCQH/AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/
+ AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/AWMBGwEJAf8BYQEcAQgB/wFhARsBCAH/AV8BGwEIAf8BXgEb
+ AQgB/wFeARsBCAH/AVsBHAELAfsBSwE0ATABrQMVAR0DAQECGAADBAEGA3gBpgN1Af8DgAH/A6IB/wOv
+ Af8DtwH/A78B/wPGAf8DygH/A8wB/wPMAf8DygH/A8UB/wO9Af8DtgH/A68B/wOiAf8DgAH/A3IB/wN9
+ AbEDDAEPKAADBAEGAnIBeAGmAjEBpAH/Ah4BywH/AUkBTAHqAf8BYgFoAe8B/wFrAXcB9gH/AXYBiQH8
+ Af8BgwGbAv8BjQGpAv8BkQGtAv8BkAGsAv8BjAGnAv8BgQGYAv8BdAGGAfsB/wFqAXUB9QH/AWEBZwHv
+ Af8BSQFMAesB/wIfAcwB/wEuAS0BoQH/AnYBfQGxAwwBDxsAAQEDNwFdA0cBgANFAYADRQGAA0YBgANG
+ AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
+ AYADRwGAA0YBgANGAYADRQGAA0UBgANHAYADMQFOAwABAQgAAwEBAgFaATcBLgG6AYMBKwENAf8BdgEl
+ AQsB/wF3ASYBDAH/AXsBKAEMAf8BfgEoAQwB/wGCASkBDQH/AYQBKgENAf8BhgEqAQ0B/wGGASsBDQH/
+ AYYBKwENAf8BhgErAQ0B/wGGASsBDQH/AYYBKwENAf8BhgErAQ0B/wGGASsBDQH/AYYBKwENAf8BhgEr
+ AQ0B/wGGASsBDQH/AYUBKgENAf8BhAEqAQ0B/wGBASkBDQH/AX0BKQEMAf8BewEnAQwB/wF3ASUBDAH/
+ AXYBJgELAf8BgwEsAQ0B/wFRAToBNQGbAwABARQAAxUBGgOIAdcDbgH/A5MB/wOpAf8DsgH/A7kB/wO+
+ Af8DwwH/A8gB/wPLAf8DygH/A8oB/wPKAf8DxwH/A8EB/wO8Af8DuAH/A7EB/wOoAf8DlAH/A20B/wOE
+ Ad4DFwEcIAADFQEaAnQBjwHXARcBFgGwAf8BNgE3Ad0B/wFaAV4B6QH/AWUBbQHxAf8BbgF7AfcB/wF3
+ AYoB+AH/AYMBmQH6Af8BjQGnAfsB/wGRAa0B/AH/AZABqgH8Af8BkAGrAfwB/wGRAa0B+wH/AYsBpQH6
+ Af8BgAGWAfgB/wF1AYcB9wH/AW0BeQH2Af8BZAFrAfAB/wFZAV0B6AH/ATcBOAHeAf8BFgEVAa8B/wJv
+ AYwB3gMXARwUAAMOARMDSAF/A0wBgANMAYADTQGAA04BgANPAYADUAGAA1ABgANQAYADUAGAA1ABgANQ
+ AYADUAGAA1ABgANQAYADUAGAA1ABgANQAYADUAGAA1ABgANQAYADTwGAA08BgANOAYADTQGAA0wBgANM
+ AYADRwF9AwQBBggAAR0CGwEmAY8BNAEQAf4BpQE9AREB/wGqAT8BEgH/AbABQQETAf8BtgFDARQB/wG7
+ AUUBFAH/AcABSAEVAf8BwwFIARUB/wHEAUkBFQH/AcUBSgEVAf8BxQFKARUB/wHFAUoBFQH/AcUBSgEV
+ Af8BxQFKARUB/wHFAUoBFQH/AcUBSgEVAf8BxQFKARUB/wHFAUoBFQH/AcUBSgEVAf8BxAFJARUB/wHC
+ AUgBFQH/Ab8BSAEVAf8BuwFFARQB/wG1AUMBFAH/Aa8BQAETAf8BqgE/ARIB/wGkATwBEQH/AYgBMgET
+ AfkDCQEMFAADhwHWA2wB/wOZAf8DpwH/A68B/wOzAf8DtwH/A7wB/wPCAf8DwwH/A8QB/wPFAf8DxQH/
+ A8QB/wPDAf8DwAH/A7sB/wO2Af8DsgH/A64B/wOmAf8DmQH/A2wB/wOFAeMDBwEJHAACcgGPAdYCDgGy
+ Af8BQgFDAd0B/wFZAV0B5gH/AWIBagHvAf8BaQF2Ae8B/wFxAYMB8AH/AXoBjwHyAf8BhAGbAfUB/wGH
+ AaAB9QH/AYkBowH2Af8BigGlAfYB/wGKAaUB9gH/AYkBowH1Af8BhwGfAfUB/wGCAZgB8wH/AXgBjQHx
+ Af8BbwGBAfAB/wFoAXQB7wH/AWEBaAHuAf8BWAFcAeUB/wFCAUMB3QH/Ag4BsgH/Am0BjgHjAwcBCRAA
+ AxgBIANLAYADUAGAA1EBgANTAYADVAGAA1UBgANWAYADYgGAA2ABgANXAYADVwGAA1cBgANXAYADVwGA
+ A1cBgANXAYADVwGAA1cBgANXAYADZQGAA1cBgANVAYADVQGAA1QBgANSAYADUQGAA1ABgANKAYADDgET
+ CAABLwEqASgBPwGiAT0BEQH/AcMBTAEVAf8BzQFQARYB/wHVAVUBGQH/AdoBWgEeAf8B3AFeASMB/wHd
+ AWEBKAH/AeoBogGAAf8B5wGVAW4B/wHfAWcBLgH/Ad8BZwEuAf8B3wFnAS4B/wHfAWcBLgH/Ad8BZwEu
+ Af8B3wFnAS4B/wHfAWcBLgH/Ad8BZwEuAf8B3wFnAS4B/wHfAWcBLgH/Ae0BswGXAf8B3wFqATMB/wHd
+ AWEBJwH/AdsBXgEiAf8B2QFZAR4B/wHUAVQBGAH/AcwBTwEWAf8BwAFLARUB/wGaATcBEAH/AR0BHAEb
+ ASYQAANzAZ0DaQH/A5QB/wOjAf8DqgH/A6wB/wOxAf8DtQH/A7kB/wO8Af8DvgH/A78B/wO/Af8DvwH/
+ A78B/wO9Af8DuwH/A7kB/wO1Af8DsAH/A6wB/wOqAf8DowH/A5YB/wNoAf8DfAGyGAACbQF0AZ0CDwGq
+ Af8BPgFAAdgB/wFVAVkB4gH/AVwBZAHpAf8BYgFuAegB/wFpAXoB6gH/AXIBhgHsAf8BeQGQAe0B/wF+
+ AZcB7gH/AYIBnAHvAf8BgwGeAfAB/wGEAZ8B8AH/AYQBngHwAf8BgwGeAfAB/wGBAZsB7wH/AX0BlgHu
+ Af8BeAGOAe0B/wFxAYQB6wH/AWgBeAHpAf8BYQFsAegB/wFcAWIB6AH/AVQBWAHiAf8CQAHZAf8CDwGp
+ Af8CdAF9AbIQAAMYASADTAGAA1EBgANTAYADVQGAA1YBgANXAYADWAGAA3EBgANoAYADWQGAA1kBgANZ
+ AYADWQGAA1kBgANZAYADWQGAA1kBgANZAYADWwGAA3QBgANoAYADWAGAA1cBgANWAYADVQGAA1MBgANR
+ AYADSgGAAw8BFAgAATABKgEoAT8BqAE/ARIB/wHOAVEBFgH/AdkBVwEaAf8B3AFfASQB/wHeAWYBLQH/
+ Ad8BawEyAf8B4QFvATgB/wH6AesB5AH/AfEBwQGpAf8B4gFzAT8B/wHiAXMBPwH/AeIBcwE/Af8B4gFz
+ AT8B/wHiAXMBPwH/AeIBcwE/Af8B4gFzAT8B/wHiAXMBPwH/AeIBcwE/Af8B5AF9AU0B/wH9AfwB+wH/
+ AfEBwAGpAf8B4QFuATcB/wHfAWoBMQH/Ad0BZQErAf8B3AFeASMB/wHYAVYBGQH/Ac0BUQEWAf8BnAE5
+ ARAB/wEeARwBGwEnDAADRgFWA2wB/wOIAf8DnwH/A6UB/wOmAf8DqgH/A68B/wOzAf8DtgH/A7gB/wO6
+ Af8DugH/A7sB/wO7Af8DugH/A7kB/wO4Af8DtgH/A7IB/wOuAf8DqQH/A6UB/wOkAf8DngH/A4oB/wNn
+ Af8DTgFiEAACRgFFAVYCJAGdAf8BLgEvAc0B/wFQAVMB3AH/AVcBXAHjAf8BWwFlAeIB/wFhAW8B5AH/
+ AWkBegHmAf8BcAGEAegB/wF1AYwB6gH/AXkBkgHsAf8BewGVAe0B/wF8AZcB7QH/AX0BmAHtAf8BfQGY
+ Ae0B/wF8AZYB7QH/AXoBlQHtAf8BeAGRAewB/wF0AYoB6gH/AW8BgwHoAf8BZwF4AeYB/wFgAW4B4wH/
+ AVkBYwHiAf8BVgFbAeMB/wFPAVIB2wH/ATABMQHQAf8BIAEfAZgB/wJOAUwBYgwAAxgBIANNAYADUgGA
+ A1QBgANWAYADVwGAA1gBgANeAYADdAGAA2kBgANaAYADWgGAA1oBgANaAYADWgGAA1oBgANaAYADWgGA
+ A1oBgANmAYADdQGAA3QBgANaAYADWAGAA1cBgANWAYADVAGAA1IBgANLAYADDwEUCAABMAEqASgBPwGr
+ AUEBEgH/AdUBUwEXAf8B2wFbASAB/wHdAWUBKwH/Ad8BawEyAf8B4QFwAToB/wHnAYwBYAH/A/4B/wHz
+ AcQBrgH/AeQBeQFHAf8B5AF5AUcB/wHkAXkBRwH/AeQBeQFHAf8B5AF5AUcB/wHkAXkBRwH/AeQBeQFH
+ Af8B5AF5AUcB/wHkAXkBRwH/Ae8BtAGYBf8B/QH7AfoB/wHjAXYBQgH/AeEBbwE5Af8B3wFqATEB/wHd
+ AWQBKQH/AdsBWwEeAf8B0wFSARcB/wGeATkBEQH/AR8BHAEbAScLAAEBA4YB6QNsAf8DmQH/A6AB/wOg
+ Af8DpAH/A6kB/wOtAf8DsQH/A7QB/wO2Af8DuAH/A7kB/wO5Af8DuQH/A7gB/wO3Af8DtgH/A7QB/wOw
+ Af8DrAH/A6gB/wOjAf8DoAH/A58B/wOYAf8DcAH/A4MB8QMBAQILAAEBAmEBnAHpAg0BtAH/AUsBTAHV
+ Af8BUQFVAd4B/wFUAVsB3AH/AVoBZAHeAf8BYQFuAeIB/wFnAXcB5QH/AW0BgAHoAf8BcQGGAeoB/wF0
+ AYsB7AH/AXYBjQHtAf8BeAGQAe0B/wF5AZEB7gH/AXgBkQHuAf8BdwGPAe0B/wF2AY0B7AH/AXMBigHr
+ Af8BcAGFAeoB/wFsAX0B5wH/AWYBdgHkAf8BYAFsAeEB/wFZAWMB3QH/AVQBWwHbAf8BUAFUAd0B/wFK
+ AUsB1AH/AhEBuAH/AloBmAHxAwEBAggAAxgBIANNAYADUwGAA1UBgANXAYADWAGAA1kBgANoAYADdQGA
+ A2kBgANbAYADWwGAA1sBgANbAYADWwGAA1sBgANbAYADWwGAA1sBgANvAYADdQGAA28BgANaAYADWQGA
+ A1gBgANWAYADVQGAA1MBgANLAYADDwEUCAABMAEqASgBPwGvAUIBEwH/AdgBVgEZAf8B3AFgASUB/wHf
+ AWgBLwH/AeEBbwE4Af8B4wFzAUAB/wHxAb4BpgX/AfMByAGyAf8B5gF9AUwB/wHmAX4BTAH/AeYBfgFM
+ Af8B5gF+AUwB/wHmAX4BTAH/AeYBfgFMAf8B5gF+AUwB/wHmAX4BTAH/AeYBfgFMAf8B+AHjAdkF/wH4
+ AeIB2AH/AeQBdwFGAf8B4gFzAT8B/wHhAW4BNwH/Ad4BaAEuAf8B3AFeASMB/wHXAVUBGAH/AaIBOwER
+ Af8BHwEcARsBJwgAA1oBcwNoAf8DiwH/A5kB/wOcAf8DngH/A6MB/wOnAf8DqwH/A64B/wOwAf8DsgH/
+ A7MB/wO1Af8DtQH/A7UB/wO1Af8DswH/A7IB/wOwAf8DrQH/A6oB/wOmAf8DogH/A50B/wObAf8DmAH/
+ A4wB/wNkAf8DZQGGCAABWQFYAVoBcwIYAZ8B/wE3ATgBywH/AUoBTQHWAf8BTwFUAdcB/wFTAVsB2AH/
+ AVoBYwHbAf8BXwFrAd8B/wFlAXQB4gH/AWkBegHkAf8BbQGBAeYB/wFwAYQB5wH/AXEBhwHoAf8BcwGK
+ AekB/wF0AYsB6QH/AXQBiwHpAf8BcwGKAekB/wFxAYYB5wH/AW8BhAHnAf8BbAGAAeYB/wFpAXkB4wH/
+ AWQBcgHhAf8BXgFqAd4B/wFZAWIB2gH/AVMBWgHXAf8BTgFTAdYB/wFJAUwB1QH/ATgBOQHMAf8CFQGb
+ Af8CYgFkAYYIAAMYASADTgGAA1MBgANWAYADVwGAA10BgANmAYADcgGAA3UBgANqAYADXAGAA1wBgANc
+ AYADXAGAA1wBgANcAYADXAGAA1wBgANfAYADdAGAA3UBgANqAYADWwGAA1oBgANZAYADVwGAA1UBgANT
+ AYADTAGAAw8BFAgAATABKgEoAT8BsgFEARMB/wHZAVgBGwH/Ad0BYwEoAf8B3wFrATIB/wHmAYoBXQH/
+ Ae8BtgGcAf8B+wHxAewF/wH0AcoBtQH/AecBggFPAf8B5wGCAU8B/wHnAYIBTwH/AecBggFPAf8B5wGC
+ AU8B/wHnAYIBTwH/AecBggFPAf8B5wGCAU8B/wHqAZQBagH/A/4F/wH0AcsBtwH/AeUBewFJAf8B4wF2
+ AUQB/wHiAXEBPAH/Ad8BagExAf8B3AFiAScB/wHZAVcBGgH/AaUBPQERAf8BHwEcARsBJwgAA4AB3ANp
+ Af8DkQH/A5YB/wOYAf8DmwH/A58B/wOjAf8DpwH/A6kB/wOrAf8DrQH/A64B/wOvAf8DrwH/A68B/wOv
+ Af8DrgH/A60B/wOrAf8DqQH/A6YB/wOjAf8DnwH/A5oB/wOYAf8DlgH/A5EB/wNrAf8DgQHxCAACXAGU
+ AdwCDQGtAf8BQwFFAcwB/wFIAUwB0gH/AUwBUQHRAf8BUQFZAdQB/wFXAWEB1wH/AVwBZwHaAf8BYgFv
+ AdwB/wFlAXUB3gH/AWgBeQHgAf8BagF9AeEB/wFtAYAB4gH/AW4BgwHjAf8BbgGDAeMB/wFuAYMB4wH/
+ AW4BgwHiAf8BbAGAAeIB/wFqAXwB4QH/AWgBeQHgAf8BZQF0Ad4B/wFhAW0B3AH/AVsBZwHaAf8BVgFf
+ AdcB/wFQAVcB0wH/AUwBUQHRAf8BSAFMAdIB/wFDAUQBywH/ARABDwGvAf8CVgGYAfEIAAMYASADTgGA
+ A1QBgANiAYADbgGAA3QBgAN1AYADdQGAA3UBgANxAYADXwGAA1wBgANcAYADXAGAA1wBgANcAYADXAGA
+ A1wBgANpAYADdQGAA3UBgANlAYADWwGAA1oBgANZAYADVwGAA1YBgANUAYADTAGAAw8BFAgAATABKgEo
+ AT8BtQFFARMB/wHaAVoBHQH/AekBoAF9Af8B9wHhAdYB/wH+Av0N/wH6Ae0B5wH/AeoBkgFmAf8B6AGF
+ AVMB/wHoAYUBUwH/AegBhQFTAf8B6AGFAVMB/wHoAYUBUwH/AegBhQFTAf8B6AGFAVMB/wHzAcUBrwn/
+ Ae8BsQGTAf8B5gF9AUsB/wHkAXcBRgH/AeIBcgE+Af8B4AFsATMB/wHdAWMBKQH/AdoBWQEcAf8BpwE9
+ ARIB/wEfARwBGwEnBAADEgEWA3cB/wN5Af8DjwH/A5IB/wOVAf8DmAH/A5sB/wOfAf8DogH/A6UB/wOn
+ Af8DqAH/A6kB/wOqAf8DqgH/A6oB/wOqAf8DqQH/A6gB/wOmAf8DpAH/A6IB/wOfAf8DmwH/A5gB/wOU
+ Af8DkgH/A48B/wN8Af8DcQH/AygBMQMSARYBNQE0AaUB/wEkASUBugH/AUEBQwHKAf8BRQFJAcwB/wFJ
+ AU8BzgH/AU4BVQHQAf8BUgFcAdMB/wFYAWIB1gH/AVwBaAHYAf8BYAFuAdoB/wFjAXIB2wH/AWQBdQHc
+ Af8BZgF4Ad0B/wFnAXkB3gH/AWcBegHeAf8BZwF5Ad4B/wFnAXkB3gH/AWYBeAHdAf8BZAF0AdwB/wFi
+ AXIB2wH/AV8BbQHZAf8BWwFnAdgB/wFXAWIB1gH/AVEBWwHTAf8BTQFUAdAB/wFJAU4BzQH/AUUBSQHM
+ Af8BQQFDAckB/wInAbwB/wIuAZ4B/wMoATEEAAMYASADTgGAA1QBgANmAYADagGAA2sBgANsAYADbQGA
+ A3QBgAN1AYADcQGAA2ABgANdAYADXQGAA10BgANdAYADXQGAA10BgANxAYADdQGAA3QBgANgAYADWwGA
+ A1oBgANZAYADVwGAA1YBgANUAYADTAGAAw8BFAgAATABKgEoAT8BtwFEARMB/wHbAVoBHgH/Ae4BtQGa
+ Af8B8wHLAbgB/wH0AdABvwH/AfYB1QHGAf8B9gHZAcsB/wH+AfwB+wX/AfsB7QHnAf8B6wGZAW8B/wHo
+ AYcBVQH/AegBhwFVAf8B6AGHAVUB/wHoAYcBVQH/AegBhwFVAf8B6AGHAVUB/wH7Ae4B6AX/A/4B/wHq
+ AZUBawH/AeYBfQFMAf8B5AF5AUcB/wHiAXMBPwH/AeABbQE0Af8B3QFkASsB/wHaAVoBHQH/AakBPQES
+ Af8BHwEcARsBJwQAA0kBXANmAf8DgwH/A40B/wOOAf8DkQH/A5UB/wOYAf8DmwH/A54B/wOgAf8DogH/
+ A6MB/wOkAf8DpQH/A6UB/wOlAf8DpAH/A6QB/wOjAf8DoQH/A6AB/wOdAf8DmgH/A5gB/wOUAf8DkQH/
+ A44B/wONAf8DhAH/A2EB/wNnAYgCRwFKAVwCGQGbAf8BMgEzAb8B/wFAAUIBxwH/AUIBRwHHAf8BRgFM
+ AcoB/wFLAVEBzQH/AU8BVwHPAf8BUwFeAdEB/wFYAWMB0wH/AVsBaAHVAf8BXQFsAdYB/wFfAW4B1wH/
+ AWEBcAHYAf8BYgFyAdgB/wFiAXMB2QH/AWIBcwHZAf8BYQFxAdgB/wFgAXAB2AH/AV4BbgHXAf8BXAFq
+ AdYB/wFaAWcB1QH/AVcBYgHTAf8BUgFdAdEB/wFOAVYBzwH/AUkBUQHNAf8BRQFKAckB/wFBAUUBxwH/
+ AT8BQQHHAf8BMwE0Ab8B/wIUAZYB/wJjAWcBiAQAAxgBIANOAYADVAGAA1YBgANYAYADWQGAA1sBgANb
+ AYADZAGAA3QBgAN1AYADcgGAA2EBgANdAYADXQGAA10BgANdAYADYwGAA3UBgAN1AYADcwGAA1wBgANb
+ AYADWgGAA1kBgANXAYADVgGAA1QBgANMAYADDwEUCAABMAEqASgBPwG4AUQBEwH/AdsBWgEfAf8B3gFm
+ ASwB/wHgAW0BNgH/AeMBcwFAAf8B5QF6AUgB/wHmAX4BTAH/Ae4BrAGMAf8C/gH9Bf8B/AHyAe4B/wHr
+ AZ0BdQH/AegBhwFVAf8B6AGHAVUB/wHoAYcBVQH/AegBhwFVAf8B7QGmAYIJ/wH9AfgB9gH/AecBggFQ
+ Af8B5gF+AUwB/wHkAXkBRwH/AeIBcwE/Af8B4AFtATQB/wHdAWUBKwH/AdoBWgEdAf8BqgE+ARIB/wEf
+ ARwBGwEnBAADcwGiA1wB/wOFAf8DigH/A4sB/wOOAf8DkQH/A5QB/wOXAf8DmQH/A5sB/wOdAf8DngH/
+ A58B/wOgAf8DoAH/A6AB/wOgAf8DnwH/A54B/wOdAf8DmwH/A5gB/wOWAf8DkwH/A5AB/wONAf8DigH/
+ A4kB/wOFAf8DXgH/A3sBvgJmAXgBogEHAQYBmAH/ATcBOAG9Af8BPQE/AcMB/wE/AUMBwgH/AUMBSAHF
+ Af8BRwFOAcgB/wFLAVQBygH/AU8BWgHMAf8BUwFeAc0B/wFWAWIBzwH/AVgBZgHQAf8BWgFpAdEB/wFb
+ AWoB0QH/AVwBbAHSAf8BXQFtAdMB/wFdAWwB0wH/AVwBbAHSAf8BWwFqAdEB/wFZAWgB0QH/AVgBZQHQ
+ Af8BVQFhAc8B/wFSAV4BzQH/AU8BWQHLAf8BSgFTAckB/wFGAU0BxwH/AUIBRwHEAf8BPQFBAcIB/wE8
+ AT8BwgH/AjcBvQH/AQkBCAGbAf8CagGCAb4EAAMYASADTgGAA1QBgANWAYADWAGAA1kBgANbAYADWwGA
+ A1wBgANlAYADdAGAA3UBgANzAYADYgGAA10BgANdAYADXQGAA2sBgAN1AYADdQGAA24BgANcAYADWwGA
+ A1oBgANZAYADVwGAA1YBgANUAYADTQGAAw8BFAgAATABKgEoAT8BuQFFARMB/wHbAVoBHwH/Ad4BZgEs
+ Af8B4AFtATYB/wHjAXMBQAH/AeUBegFIAf8B5gF+AUwB/wHnAYIBUAH/Ae8BsQGTAf8D/gX/AfwB9gH0
+ Af8B7AGkAYAB/wHoAYcBVQH/AegBhwFVAf8B6AGHAVUB/wH1AdEBvwn/AfgB4AHUAf8B5wGCAU8B/wHm
+ AX4BTAH/AeQBeQFHAf8B4gFzAT8B/wHgAW0BNAH/Ad0BZQErAf8B2gFaAR0B/wGrAT8BEgH/AR8BHAEb
+ AScEAAN9AckDXwH/A4IB/wOGAf8DhwH/A4oB/wONAf8DjwH/A5EB/wOUAf8DlgH/A5cB/wOYAf8DmgH/
+ A5oB/wOaAf8DmgH/A5oB/wOaAf8DmAH/A5cB/wOWAf8DkwH/A5EB/wOPAf8DjQH/A4kB/wOGAf8DhQH/
+ A4IB/wNiAf8DfAHUAmIBiwHJAQgBBwGeAf8CNgG5Af8BOQE8Ab4B/wE7AT4BvgH/AT8BRAHBAf8BQgFJ
+ AcMB/wFFAU4BxQH/AUkBUwHHAf8BTQFYAckB/wFQAVwBygH/AVIBXwHLAf8BUwFhAcwB/wFVAWMBzQH/
+ AVYBZQHNAf8BVgFlAc0B/wFWAWUBzQH/AVYBZQHNAf8BVQFjAc0B/wFTAWABywH/AVIBXgHLAf8BTwFb
+ AcoB/wFMAVgByAH/AUkBUgHGAf8BRQFNAcUB/wFCAUgBwwH/AT4BRAHAAf8BOwE+Ab0B/wE5ATsBvQH/
+ AjUBuQH/AgsBoQH/Al0BiwHUBAADGAEgA08BgANUAYADVgGAA1gBgANZAYADWwGAA1sBgANcAYADXAGA
+ A2gBgAN0AYADdQGAA3MBgANkAYADXQGAA2UBgAN0AYADdQGAA3UBgANoAYADXAGAA1sBgANaAYADWQGA
+ A1cBgANWAYADVAGAA00BgAMPARQIAAEwASoBKAE/AboBRQETAf8B2wFbAR8B/wHeAWYBLAH/AeABbQE2
+ Af8B4wFzAUAB/wHlAXoBSAH/AeYBfgFMAf8B5wGCAVAB/wHoAYYBVAH/AfIBwAGoAf8D/gX/Af0B+gH4
+ Af8B7gGqAYgB/wHoAYcBVQH/Ae8BsAGRAf8B/gH9AfwJ/wHzAcIBqgH/AecBggFPAf8B5gF+AUwB/wHk
+ AXkBRwH/AeIBcwE/Af8B4AFtATQB/wHdAWUBKwH/AdoBWgEdAf8BrAE/ARIB/wEfARwBGwEnBAADdgHH
+ A2IB/wN+Af8DggH/A4MB/wOFAf8DiAH/A4sB/wONAf8DjgH/A5EB/wOSAf8DkwH/A5QB/wOUAf8DlAH/
+ A5QB/wOUAf8DkwH/A5MB/wOSAf8DkAH/A44B/wOMAf8DigH/A4cB/wOFAf8DgwH/A4IB/wN9Af8DYwH/
+ A3oB3QJZAYYBxwILAZ8B/wIzAbUB/wE2ATgBuAH/ATcBOgG5Af8BOgE/AbsB/wE+AUMBvgH/AUEBSAHA
+ Af8BRAFMAcEB/wFGAVABwwH/AUkBVQHFAf8BSwFXAcUB/wFNAVkBxgH/AU8BXAHHAf8BTwFdAcYB/wFP
+ AV0BxwH/AU8BXQHHAf8BTwFdAcYB/wFOAVwBxgH/AU0BWQHGAf8BSwFXAcUB/wFJAVQBxAH/AUcBUAHC
+ Af8BQwFMAcEB/wFAAUcBvwH/AT0BQwG9Af8BOgE+AbsB/wE3ATsBuQH/ATYBNwG4Af8CMgG0Af8CDQGh
+ Af8CVgGOAd0EAAMYASADTwGAA1QBgANWAYADWAGAA1kBgANbAYADXAGAA10BgANeAYADXwGAA2wBgAN0
+ AYADdQGAA3QBgANrAYADdAGAA3UBgAN1AYADdQGAA2YBgANdAYADXAGAA1sBgANZAYADVwGAA1YBgANU
+ AYADTQGAAw8BFAgAATABKgEoAT8BugFFARMB/wHbAVsBHwH/Ad4BZgEsAf8B4AFtATYB/wHjAXMBQQH/
+ AeUBfQFNAf8B5gGFAVYB/wHnAYoBXQH/AegBjwFiAf8B6AGSAWcB/wH1AdYBxwH/A/4F/wH+AfwB+wH/
+ AfMBzgG8Af8B/gL8Df8B7wG0AZcB/wHnAYkBXAH/AeYBhQFVAf8B5AF8AUsB/wHiAXMBPwH/AeABbQE0
+ Af8B3QFlASsB/wHaAVoBHQH/AawBPwESAf8BHwEcARsBJwQAA3UByANeAf8DegH/A30B/wN+Af8DgQH/
+ A4MB/wOEAf8DhgH/A4gB/wOKAf8DiwH/A40B/wONAf8DjQH/A40B/wONAf8DjQH/A40B/wOMAf8DiwH/
+ A4kB/wOHAf8DhQH/A4QB/wODAf8DgQH/A30B/wN9Af8DegH/A2AB/wN4AdwCWAGGAcgBCQEIAZsB/wEv
+ ATABsAH/ATIBNAGzAf8BNAE3AbQB/wE2ATsBtgH/ATkBPgG4Af8BOwFBAbkB/wE9AUUBuwH/AT8BSAG8
+ Af8BQgFMAb0B/wFDAU4BvgH/AUYBUgHAAf8BRwFUAcAB/wFHAVQBwAH/AUcBVAHAAf8BRwFUAcAB/wFH
+ AVQBwAH/AUcBUwHAAf8BRQFRAb8B/wFDAU4BvgH/AUEBSwG9Af8BPwFHAbsB/wE8AUUBugH/AToBQQG5
+ Af8BOAE+AbcB/wE2AToBtQH/ATMBNgGzAf8BMgEzAbMB/wIvAbAB/wELAQoBnQH/AlQBjAHcBAADGAEg
+ A08BgANUAYADWAGAA1wBgANeAYADXwGAA18BgANgAYADYAGAA2ABgANhAYADbgGAA3UBgAN1AYADdQGA
+ A3UBgAN1AYADdQGAA3QBgANiAYADYAGAA18BgANfAYADXgGAA1sBgANYAYADVAGAA00BgAMPARQIAAEw
+ ASoBKAE/AboBRQETAf8B2wFcASAB/wHfAXEBOwH/AeIBggFTAf8B5AGNAWIB/wHmAZIBaQH/AecBlAFt
+ Af8B6AGWAW8B/wHoAZkBcQH/AegBmQFyAf8B6QGbAXUB/wH3Ad4B0xn/A/4B/wHqAaQBgwH/AegBlQFv
+ Af8B5wGUAWwB/wHmAZEBaAH/AeQBjQFhAf8B4gGBAVEB/wHfAXABOgH/AdoBWwEeAf8BrAE/ARIB/wEf
+ ARwBGwEnBAADdQHCA1cB/wN2Af8DeQH/A3oB/wN6Af8DfAH/A4EB/wOEAf8DhgH/A4gB/wOJAf8DiwH/
+ A4wB/wOMAf8DjAH/A4wB/wOMAf8DjAH/A4sB/wOJAf8DiAH/A4YB/wOEAf8DgQH/A3sB/wN6Af8DeQH/
+ A3gB/wN2Af8DWgH/A3YB0gJaAYMBwgIBAZIB/wIsAasB/wEvATABrwH/ATABMwGvAf8BMAE0Aa8B/wEy
+ ATgBsQH/ATgBPgG0Af8BPAFDAbYB/wE/AUcBuAH/AUIBSwG6Af8BRAFNAboB/wFGAVABvAH/AUcBUgG9
+ Af8BRwFTAb0B/wFIAVMBvAH/AUgBUwG9Af8BRwFSAb0B/wFHAVIBvQH/AUYBUAG8Af8BQwFMAboB/wFC
+ AUoBuQH/AT8BRwG4Af8BPAFCAbYB/wE4AT4BtAH/ATIBNwGwAf8BMAEzAa8B/wEvATIBrgH/AS4BMAGu
+ Af8CKwGrAf8CBAGVAf8CVgGGAdIEAAMYASADTwGAA1cBgANdAYADXgGAA18BgANfAYADYAGAA2EBgANh
+ AYADYQGAA2EBgANhAYADcQGAA3UBgAN1AYADdQGAA3UBgAN1AYADcgGAA2EBgANgAYADYAGAA18BgANf
+ AYADXgGAA1wBgANXAYADTQGAAw8BFAgAATABKgEoAT8BugFFARMB/wHcAWkBMwH/AeIBhwFcAf8B4wGO
+ AWMB/wHlAZIBaQH/AeYBlQFtAf8B5wGYAXIB/wHoAZoBdQH/AegBnAF2Af8B6AGcAXcB/wHoAZwBdwH/
+ AegBnwF7Af8B+gHtAecV/wH8AfQB8QH/AegBmwF3Af8B6AGZAXQB/wHnAZcBcQH/AeYBlQFsAf8B5QGS
+ AWkB/wHjAY0BYgH/AeEBhwFbAf8B3AFpATIB/wGsAT8BEgH/AR8BHAEbAScEAANpAZoDUQH/A3IB/wN2
+ Af8DdAH/A3gB/wOCAf8DhwH/A4kB/wOKAf8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjgH/A40B/wON
+ Af8DjAH/A4wB/wOKAf8DiQH/A4gB/wOGAf8DgQH/A3cB/wNzAf8DdQH/A3EB/wNSAf8DdAG8AlwBbwGa
+ AgABhwH/AigBpgH/ASwBLQGqAf8BKAErAakB/wEuATIBrAH/AT0BQQGyAf8BQwFHAbUB/wFFAUoBtwH/
+ AUcBTQG4Af8BSQFPAbkB/wFKAVIBugH/AUsBUwG7Af8BTAFUAbwB/wFMAVQBvAH/AU0BVQG8Af8BTQFV
+ AbwB/wFMAVQBuwH/AUwBUwG7Af8BSwFSAboB/wFKAVEBugH/AUgBTwG4Af8BRgFNAbcB/wFEAUkBtgH/
+ AUIBRgG1Af8BPAFAAbEB/wEuATEBqwH/ASgBKgGoAf8CLAGpAf8BKAEnAaUB/wIAAYoB/wJgAXwBvAQA
+ AxgBIANPAYADWAGAA14BgANfAYADXwGAA2ABgANhAYADYQGAA2IBgANiAYADYgGAA2kBgAN0AYADdQGA
+ A3UBgAN1AYADdQGAA3UBgANvAYADYgGAA2EBgANhAYADYAGAA18BgANeAYADXQGAA1gBgANNAYADDwEU
+ CAABMAEqASgBPwG6AUUBEwH/Ad0BbQE7Af8B4gGLAWMB/wHkAZEBaQH/AeUBlQFuAf8B5gGaAXQB/wHn
+ AZwBeAH/AegBngF7Af8B6QGgAXwB/wHpAaABfQH/AekBoAF9Af8B8AHFAbAB/wP+Ff8B+AHkAdoB/wHp
+ AaABfAH/AegBngF6Af8B5wGcAXcB/wHmAZoBcwH/AeUBlQFuAf8B5AGRAWgB/wHiAYsBYgH/Ad0BbQE5
+ Af8BrAE/ARIB/wEfARwBGwEnBAADSAFfA1EB/wNrAf8DbwH/A3MB/wOFAf8DiAH/A4gB/wOJAf8DigH/
+ A4sB/wOMAf8DjQH/A44B/wOOAf8DjgH/A44B/wOOAf8DjgH/A40B/wOMAf8DiwH/A4kB/wOIAf8DiAH/
+ A4gB/wOEAf8DcwH/A28B/wNsAf8DTQH/A2MBhwJGAUkBXwIDAYMB/wEhASABoAH/AiUBowH/ASoBLAGm
+ Af8BQwFFAbEB/wFIAUsBtAH/AUcBSwG0Af8BSQFNAbUB/wFKAU8BtgH/AUsBUQG3Af8BTQFTAbcB/wFO
+ AVQBuAH/AU8BVQG5Af8BTwFWAbkB/wFQAVYBuQH/AVABVgG5Af8BTwFWAbkB/wFPAVUBuAH/AU4BUwG3
+ Af8BTQFSAbcB/wFLAVEBtgH/AUkBTwG1Af8BSAFNAbQB/wFHAUoBtAH/AUgBSwG0Af8BQgFEAbAB/wEq
+ ASwBpQH/AiUBowH/AiEBoAH/AgABfQH/Al0BZAGHBAADGAEgA04BgANZAYADXwGAA18BgANgAYADYQGA
+ 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
+ Af8B6QGuAZIB/wHnAaMBhQH/AeYBogGBAf8B5QGdAXwB/wHkAZkBdgH/Ad4BegFKAf8BqwE+ARIB/wEf
+ ARwBGwEnCAADUgFwA0cB/wNZAf8DeQH/A5UB/wOMAf8DjgH/A48B/wOQAf8DkAH/A5IB/wOSAf8DkgH/
+ A5IB/wOTAf8DkwH/A5IB/wOSAf8DkgH/A5EB/wOQAf8DjwH/A48B/wOOAf8DjAH/A5QB/wN5Af8DWgH/
+ A0YB/wNiAYoIAAJNAVQBcAIAAXEB/wIKAY0B/wI6AaQB/wJhAbYB/wJWAa8B/wJXAbEB/wFYAVkBsgH/
+ AVkBWgGzAf8BWgFbAbMB/wFcAV0BtAH/AVwBXgG0Af8BXAFeAbUB/wFcAV8BtAH/AV0BXwG1Af8BXQFf
+ AbUB/wFcAV8BtAH/AVwBXgG0Af8BXAFfAbQB/wFbAV0BtAH/AVoBWwGzAf8BWQFaAbIB/wJYAbIB/wJX
+ AbEB/wFXAVYBrwH/AmEBtQH/AjoBpAH/AgsBjgH/AgABbgH/AlsBZAGKCAADGAEgA04BgANbAYADaAGA
+ A3IBgAN0AYADdQGAA3UBgAN1AYADdQGAA3QBgANzAYADbwGAA2wBgANoAYADZQGAA2QBgANmAYADcgGA
+ A3UBgAN1AYADdQGAA3EBgANjAYADYgGAA2IBgANhAYADWwGAA0wBgAMPARQIAAEwASoBKAE/AbYBRAET
+ Af8B3wF9AVAB/wHtAcIBrwH/AfsB9AHxAf8D/hH/Af4C/QH/AfwB9wH1Af8B9gHhAdkB/wHzAdMBxwH/
+ Ae0BvgGrAf8B6gGvAZcB/wHoAakBjwH/AesBtQGfAf8B+wH0AfEN/wH5AewB5gH/AegBqgGNAf8B5gGl
+ AYYB/wHlAaEBgwH/AeQBngF9Af8B3gF7AU8B/wGpAT0BEgH/AR8BHAEbAScMAANlAegDSAH/A3EB/wOb
+ Af8DkAH/A5EB/wORAf8DkgH/A5MB/wOUAf8DlAH/A5QB/wOUAf8DlQH/A5UB/wOUAf8DlAH/A5QB/wOU
+ Af8DkwH/A5IB/wORAf8DkQH/A5AB/wObAf8DcQH/A0kB/wNfAfMDBAEGDAACNQF/AegCAAFyAf8CMAGc
+ Af8CawG5Af8CXQGwAf8CXQGxAf8CXwGxAf8CXwGyAf8BYAFhAbMB/wFhAWIBtAH/AWEBYgG0Af8BYQFj
+ AbQB/wFiAWMBtAH/AWIBZAG1Af8BYgFkAbUB/wFiAWQBtAH/AWEBYgG0Af8BYQFiAbQB/wFhAWIBtAH/
+ AWABYQGzAf8CXwGyAf8CXwGxAf8BXgFdAbEB/wJdAbAB/wJrAbgB/wIwAZwB/wIAAXQB/wIsAX0B8wME
+ AQYIAAMYASADTgGAA1sBgANkAYADcQGAA3QBgAN0AYADcAGAA20BgANqAYADZgGAA2QBgANkAYADZAGA
+ A2QBgANkAYADZAGAA2QBgANmAYADcgGAA3UBgAN1AYADdQGAA28BgANjAYADYwGAA2IBgANbAYADTAGA
+ Aw8BFAgAATABKgEoAT8BswFEARMB/wHeAYIBVgH/AecBrAGRAf8B+QHtAegB/wP+Af8B/QH6AfkB/wH4
+ AeoB5AH/AfQB2QHNAf8B7wHKAbgB/wHqAbYBngH/AekBsAGUAf8B6AGuAZIB/wHoAa4BkgH/AegBrgGS
+ Af8B6AGuAZIB/wHoAa4BkgH/AegBrgGSAf8B6wG4AaAB/wH6AfIB7g3/AfYB4QHYAf8B5gGnAYsB/wHm
+ AaUBiAH/AeUBowGEAf8B3gGBAVUB/wGmAT0BEQH/AR8CHAEnDAADQgFVA0IB/wNVAf8DjgH/A54B/wOT
+ Af8DlQH/A5YB/wOWAf8DlwH/A5cB/wOXAf8DmAH/A5gB/wOYAf8DlwH/A5cB/wOXAf8DlgH/A5YB/wOV
+ Af8DlQH/A5MB/wOdAf8DjwH/A1YB/wM/Af8DUAFoEAACQAFDAVUCAAFmAf8CDAGCAf8CWwGuAf8CcQG6
+ Af8CYgGyAf8CZQGzAf8CZgGzAf8BZgFnAbMB/wJnAbQB/wJnAbQB/wFnAWgBtAH/AWgBaQG1Af8BaAFp
+ AbUB/wFoAWkBtQH/AWcBaQG1Af8BZwFoAbQB/wJnAbQB/wFmAWcBswH/AWYBZwGzAf8CZgGyAf8CZQGy
+ Af8CYgGxAf8CcQG5Af8CXAGvAf8CDAGEAf8CAAFgAf8CTQFQAWgMAAMYASADTQGAA1wBgANjAYADZAGA
+ A2YBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZgGA
+ A3EBgAN1AYADdQGAA3UBgANtAYADYwGAA2MBgANcAYADSwGAAw8BFAgAATABKgEoAT8BsAFDARMB/wHd
+ AYQBXAH/AeUBpAGKAf8B5wGsAZQB/wHpAbkBogH/AecBsAGVAf8B5wGvAZIB/wHoAa8BkwH/AegBsQGV
+ Af8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6AGwAZUB/wHo
+ AbABlQH/AegBsAGVAf8B6gG3AZ8B/wH6AfAB6w3/AfMB2QHNAf8B5gGmAYwB/wHlAaQBiQH/Ad0BhAFb
+ Af8BowE9AREB/wEfAhwBJxAAA2cBoQNAAf8DYgH/A6gB/wOhAf8DmQH/A5oB/wOaAf8DmwH/A5sB/wOb
+ Af8DmwH/A5sB/wObAf8DmwH/A5sB/wOaAf8DmgH/A5oB/wOaAf8DmQH/A6AB/wOoAf8DZAH/Az4B/wNt
+ AbYYAAJYAW4BoQIAAWEB/wIgAYwB/wKCAcAB/wJ3AboB/wJrAbQB/wJuAbUB/wJuAbUB/wJuAbYB/wFv
+ AW4BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/AW8BbgG1Af8CbgG1Af8CbgG1
+ Af8CbgG1Af8CawG0Af8CdgG5Af8CggHAAf8CIQGOAf8CAAFcAf8CWQF2AbYQAAMYASADTQGAA10BgANj
+ AYADZAGAA2QBgANkAYADZAGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANl
+ AYADZQGAA2YBgANxAYADdQGAA3UBgAN0AYADZQGAA2MBgANdAYADSwGAAw8BFAgAATABKgEoAT8BrgFD
+ ARMB/wHdAYoBZAH/AeUBqAGOAf8B5gGqAZAB/wHmAa0BkgH/AecBrgGUAf8B5wGuAZYB/wHoAa8BlwH/
+ AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGv
+ AZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHpAbUBoAH/AfgB7AHnCf8D/gH/AecBrwGYAf8B5QGo
+ AY0B/wHcAYoBYwH/AaIBPQERAf8BHwIcAScTAAEBA10B1QNDAf8DbAH/A7IB/wOuAf8DnwH/A6EB/wOh
+ Af8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A58B/wOrAf8DswH/A28B/wM9
+ Af8DXwHjAwYBCBsAAQECNwFwAdUCAAFnAf8CLwGRAf8CkQHHAf8CiwHDAf8CdQG3Af8CeAG5Af8CeQG5
+ Af8CeQG6Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeAG5
+ Af8CdQG3Af8CiAHBAf8CkgHIAf8CMwGVAf8CAAFbAf8CNwFzAeMDBgEIEAADGAEgA00BgANcAYADZAGA
+ A2QBgANkAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGA
+ A2UBgANlAYADZgGAA3ABgAN0AYADcgGAA2UBgANkAYADXAGAA0sBgAMPARQIAAEwASoBKAE/AawBQgES
+ Af8B2wGJAWEB/wHlAakBkgH/AeYBrAGUAf8B5gGvAZYB/wHmAbABmAH/AecBsQGZAf8B5wGwAZoB/wHn
+ AbIBmgH/AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/AecBswGb
+ Af8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGyAZoB/wHoAbQBngH/AfgB6gHjAf8D/gH/AfoB8gHv
+ Af8B5gGvAZgB/wHlAakBkQH/AdoBigFhAf8BoQE8AREB/wEfARwBGwEnFAADDgESA1AB0QM/Af8DZAH/
+ A6kB/wPCAf8DsQH/A6cB/wOoAf8DqQH/A6kB/wOpAf8DqQH/A6kB/wOpAf8DqAH/A6cB/wOvAf8DwQH/
+ A64B/wNnAf8DOQH/A10B3AMWARsgAAMOARICKAFhAdECAAFfAf8CJwGJAf8ChQG/Af8CqAHSAf8CkQHE
+ Af8CgwG8Af8ChQG9Af8ChQG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChQG9Af8CgwG8
+ Af8CjgHCAf8CpwHRAf8CiwHDAf8CLAGLAf8CAAFSAf8COQFuAdwDFgEbFAADEQEXA0sBgANXAYADZAGA
+ A2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGA
+ A2UBgANlAYADZQGAA2UBgANnAYADZgGAA2UBgANkAYADVwGAA0kBfgMGAQgIAAEjASABHwEtAZ4BOwER
+ Af8B0QFuAT4B/wHmAasBlwH/AeYBsAGZAf8B5gGzAZoB/wHnAbQBmwH/AecBtQGcAf8B5wG2AZwB/wHn
+ AbYBnAH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGd
+ Af8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG1AZwB/wHnAbYBnAH/AecBtwGeAf8B6QG/AagB/wHn
+ AbcBnwH/AeYBsAGZAf8B5gGrAZcB/wHRAW8BPgH/AZIBNgERAfwBDQIMARAYAAMEAQUDZQGlAzkB/wNF
+ Af8DhAH/A7MB/wPKAf8DxAH/A70B/wO1Af8DswH/A7MB/wO0Af8DuwH/A8QB/wPKAf8DugH/A4wB/wNF
+ Af8DNQH/A2kBsQMKAQ0oAAMEAQUCVQFtAaUCAAFTAf8CAAFsAf8CVAGgAf8ClQHFAf8CtAHXAf8CrQHT
+ Af8CogHNAf8CmAHHAf8ClAHFAf8ClAHFAf8ClwHGAf8CoAHLAf8CrAHSAf8CtQHXAf8CngHKAf8CXgGm
+ Af8CAAFtAf8CAAFJAf8CVwFxAbEDCgENHAADPQFkA08BgANXAYADXgGAA2ABgANgAYADYAGAA2EBgANh
+ AYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYAGAA2ABgANg
+ AYADXgGAA1cBgANPAYADNwFVEAABcgE+ASwBxwG/AU0BFQH/AdIBcAFBAf8B3AGRAXAB/wHfAZwBeQH/
+ AeABmwF6Af8B4gGdAXoB/wHjAZ8BewH/AeMBnwF8Af8B4wGgAXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGf
+ AXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGfAXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGgAXwB/wHjAZ8BewH/
+ AeMBnwF7Af8B4gGcAXoB/wHgAZsBegH/Ad4BnAF5Af8B3AGRAXAB/wHSAXABQQH/AbsBSgEUAf8BZwFD
+ ATYBqSQAA00BdANPAeYDOAH/A0oB/wN9Af8DlwH/A6wB/wO/Af8DxgH/A8cB/wPBAf8DsQH/A5oB/wOD
+ Af8DSgH/AzQB/wNJAewDWAF7OAACRgFQAXQCHgFnAeYCAAFQAf8CBAFyAf8CSwGaAf8CbwGuAf8CjAG+
+ Af8CpgHNAf8CsAHTAf8CsQHUAf8CqQHPAf8CkgHCAf8CcgGwAf8CUgGeAf8CBAFyAf8CAAFIAf8CGAFg
+ AewCUwFaAXskAAMMAQ8DPQFkA0sBgANNAYADTQGAA00BgANOAYADTgGAA04BgANOAYADTgGAA04BgANO
+ AYADTgGAA04BgANOAYADTgGAA04BgANOAYADTgGAA04BgANOAYADTQGAA00BgANNAYADSgF/AzkBWwMG
+ AQgQAAEXARYBFQEdAXIBPgEsAccBngE7AREB/wGsAUIBEgH/Aa0BQwETAf8BsAFDARMB/wGzAUQBEwH/
+ AbYBRAETAf8BuAFFARMB/wG5AUUBEwH/AbkBRQETAf8BuQFFARMB/wG5AUUBEwH/AbkBRQETAf8BuQFF
+ ARMB/wG5AUUBEwH/AbkBRQETAf8BuQFFARMB/wG5AUUBEwH/AbgBRQETAf8BtgFEARMB/wGzAUQBEwH/
+ AbABQwETAf8BrQFDARMB/wGsAUIBEgH/AZoBOgERAf4BbAFBATIBtQENAgwBECgAAwoBDQNbAZADSQHe
+ AzwB/wM5Af8DQAH/A04B/wNiAf8DZAH/A1AB/wNAAf8DOgH/AzoB/wNPAeADYQGWAw4BEkAAAwoBDQJQ
+ AWABkAIcAV8B3gIAAVkB/wIAAVMB/wIAAWIB/wIJAXUB/wIlAYYB/wIoAYgB/wIMAXYB/wIAAWEB/wIA
+ AVQB/wIAAVQB/wIjAWMB4AJXAWUBlgMOARIwAAMSARcDGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEg
+ AxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEg
+ Aw4BEyAAASMBIAEfAS0BMAErASgBPwEwASsBKQE/ATABKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEp
+ AT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwEx
+ ASsBKQE/ATEBKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwEwASsBKQE/ATABKwEp
+ AT8BMAErASgBPwEeARwBGwEmPAADMAFAA1UBigNYAb4DTAHsA0EB8gNAAfIDTQHuA1wBwANVAY0DNQFE
+ WAACLgEwAUACSgFaAYoCPAFlAb4CGgFkAewCCgFZAfICCgFZAfICGgFkAe4CQQFpAcACSgFZAY0CMwE1
+ AUT/AC0AAUIBTQE+BwABPgMAASgDAAGAAwABQAMAAQEBAAEBBgABBBYAA///AP8AAwAB/wHgAQMC/wHg
+ AQMB/wHAAgABAwHAAgABAwH/AgAC/wIAAf8BgAIAAQMBgAIAAQMB/gIAAX8B/gIAAX8BgAIAAQEBgAIA
+ AQEB+AIAAR8B+AIAAR8BgAIAAQEBgAIAAQEB8AIAAQ8B8AIAAQ8BgAIAAQEBgAIAAQEB8AIAAQcB8AIA
+ AQcBgAIAAQEBgAIAAQEB4AIAAQcB4AIAAQcBgAIAAQEBgAIAAQEBwAIAAQMBwAIAAQMBgAIAAQEBgAIA
+ AQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIA
+ AQEBgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGA
+ AgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEB
+ AYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGA
+ AgABAQGAAgABAQGAAgABAQGAAgABAQHAAgABAQHAAgABAQGAAgABAQGAAgABAQHAAgABAwHAAgABAwGA
+ AgABAQGAAgABAQHgAgABBwHgAgABBwGAAgABAQGAAgABAQHgAgABBwHgAgABBwGAAgABAQGAAgABAQHw
+ AgABDwHwAgABDwGAAgABAQGAAgABAQH4AgABHwH4AgABHwHAAgABAwHAAgABAwH+AgABfwH+AgABfwHA
+ AgABAwHAAgABAwH/AgAC/wIAAf8B8AIAAQ8B8AIAAQ8B/wHgAQcC/wHgAQcJ/ws=
+
+
+
\ No newline at end of file
diff --git a/FSFlightLogger/Program.cs b/FSFlightLogger/Program.cs
new file mode 100644
index 0000000..4b76b3c
--- /dev/null
+++ b/FSFlightLogger/Program.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Windows.Forms;
+using SimConnect;
+using SimConnect.Concrete;
+using SimpleInjector;
+using SimpleInjector.Diagnostics;
+
+namespace FSFlightLogger
+{
+ public static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ public static void Main()
+ {
+ var container = CreateContainer();
+
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(container.GetInstance());
+ }
+
+
+ public static Container CreateContainer()
+ {
+ var container = new Container();
+
+ // Since the MainForm is registered as well, do not call Verify on the container as it would create an instance
+ container.Options.EnableAutoVerification = false;
+
+ container.Register();
+ container.Register();
+
+ return container;
+ }
+ }
+}
diff --git a/FSFlightLogger/Properties/AssemblyInfo.cs b/FSFlightLogger/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d2b856f
--- /dev/null
+++ b/FSFlightLogger/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("FSFlightLogger")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("FSFlightLogger")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("5871fa9b-88a7-4f98-8755-2fc48d4dbf44")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/FSFlightLogger/Properties/Resources.Designer.cs b/FSFlightLogger/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..680bb11
--- /dev/null
+++ b/FSFlightLogger/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace FSFlightLogger.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // 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", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FSFlightLogger.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/FSFlightLogger/Properties/Resources.resx b/FSFlightLogger/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/FSFlightLogger/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/FSFlightLogger/Properties/Settings.Designer.cs b/FSFlightLogger/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..338a6d3
--- /dev/null
+++ b/FSFlightLogger/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace FSFlightLogger.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/FSFlightLogger/Properties/Settings.settings b/FSFlightLogger/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/FSFlightLogger/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/FSFlightLogger/Resources/Images/FSXConnected.png b/FSFlightLogger/Resources/Images/FSXConnected.png
new file mode 100644
index 0000000..d67a18e
Binary files /dev/null and b/FSFlightLogger/Resources/Images/FSXConnected.png differ
diff --git a/FSFlightLogger/Resources/Images/FSXDisconnected.png b/FSFlightLogger/Resources/Images/FSXDisconnected.png
new file mode 100644
index 0000000..d4a892d
Binary files /dev/null and b/FSFlightLogger/Resources/Images/FSXDisconnected.png differ
diff --git a/FSFlightLogger/Resources/Images/Idle.png b/FSFlightLogger/Resources/Images/Idle.png
new file mode 100644
index 0000000..758c3c4
Binary files /dev/null and b/FSFlightLogger/Resources/Images/Idle.png differ
diff --git a/FSFlightLogger/Resources/Images/P3DConnected.png b/FSFlightLogger/Resources/Images/P3DConnected.png
new file mode 100644
index 0000000..f808c85
Binary files /dev/null and b/FSFlightLogger/Resources/Images/P3DConnected.png differ
diff --git a/FSFlightLogger/Resources/Images/P3DDisconnected.png b/FSFlightLogger/Resources/Images/P3DDisconnected.png
new file mode 100644
index 0000000..bdff778
Binary files /dev/null and b/FSFlightLogger/Resources/Images/P3DDisconnected.png differ
diff --git a/FSFlightLogger/Resources/Images/Recording.png b/FSFlightLogger/Resources/Images/Recording.png
new file mode 100644
index 0000000..0d75d2c
Binary files /dev/null and b/FSFlightLogger/Resources/Images/Recording.png differ
diff --git a/FSFlightLogger/packages.config b/FSFlightLogger/packages.config
new file mode 100644
index 0000000..bcba459
--- /dev/null
+++ b/FSFlightLogger/packages.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FSFlightLoggerCmd/App.config b/FSFlightLoggerCmd/App.config
new file mode 100644
index 0000000..56efbc7
--- /dev/null
+++ b/FSFlightLoggerCmd/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FSFlightLoggerCmd/FSFlightLoggerCmd.csproj b/FSFlightLoggerCmd/FSFlightLoggerCmd.csproj
new file mode 100644
index 0000000..1288739
--- /dev/null
+++ b/FSFlightLoggerCmd/FSFlightLoggerCmd.csproj
@@ -0,0 +1,86 @@
+
+
+
+
+ Debug
+ x86
+ {767C7EAA-9230-4DEE-89FA-9699288C831B}
+ Exe
+ FSFlightLoggerCmd
+ FSFlightLoggerCmd
+ v4.7.2
+ 512
+ true
+ true
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ 7.3
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ true
+ bin\x64\Debug\
+ x64
+ 7.3
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ 7.3
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ true
+ bin\x86\Debug\
+ x86
+ 7.3
+ MinimumRecommendedRules.ruleset
+ true
+
+
+
+ ..\packages\CommandLineParser.2.8.0\lib\net461\CommandLine.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {D85BCC97-F653-4286-98D9-073A33A55857}
+ FlightLoggerLib
+
+
+ {f160bb6a-7620-41e5-a99c-948c208875e4}
+ SimConnect
+
+
+
+
\ No newline at end of file
diff --git a/FSFlightLoggerCmd/Program.cs b/FSFlightLoggerCmd/Program.cs
new file mode 100644
index 0000000..6c7f9cb
--- /dev/null
+++ b/FSFlightLoggerCmd/Program.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using CommandLine;
+using FlightLoggerLib;
+using FlightLoggerLib.Concrete;
+using SimConnect;
+using SimConnect.Attribute;
+using SimConnect.Concrete;
+using SimConnect.Lib;
+
+namespace FSFlightLoggerCmd
+{
+ // TODO verb for converting CSV to KML
+
+
+ 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 class Program
+ {
+ private enum OutputFormat
+ {
+ None,
+ CSV,
+ KML
+ };
+
+
+ // ReSharper disable once ClassNeverInstantiated.Local - used by CommandLineParser
+ // ReSharper disable UnusedAutoPropertyAccessor.Local - used by CommandLineParser
+ private class Options
+ {
+ [Option('o', "outputPath", Required = false, HelpText = "Specifies the output path for the log files. Defaults to a 'Flight logs' folder on the desktop.")]
+ public string OutputPath { get; set; }
+
+ [Option('i', "interval", Required = false, Default = 1, HelpText = "The minimum time, in seconds, between log entries.")]
+ public int IntervalTime { get; set; }
+
+ [Option('d', "distance", Required = false, Default = 1, HelpText = "The minimum distance, in meters, between log entries.")]
+ public int IntervalDistance { get; set; }
+
+ [Option('v', "verbose", Required = false, HelpText = "Enable verbose logging.")]
+ public bool Verbose { get; set; }
+
+ [Option('f', "format", Required = false, Default = OutputFormat.CSV, HelpText = "The output format to use. Possible values: CSV, KML.")]
+ public OutputFormat OutputFormat { get; set; }
+
+ [Option('s', "format2", Required = false, Default = OutputFormat.None, HelpText = "The secondary output format to use. Possible values: None, CSV, KML.")]
+ public OutputFormat OutputFormat2 { get; set; }
+ }
+ // ReSharper restore UnusedAutoPropertyAccessor.Local
+
+
+ public static void Main(string[] args)
+ {
+ var parser = new Parser(settings =>
+ {
+ settings.CaseInsensitiveEnumValues = true;
+ });
+
+ parser.ParseArguments(args)
+ .WithParsed(Run);
+ }
+
+ private static void Run(Options o)
+ {
+ var intervalTime = TimeSpan.FromSeconds(o.IntervalTime);
+ void VerboseLog(string message)
+ {
+ if (o.Verbose)
+ Console.WriteLine(message);
+ }
+
+
+ var factory = new SimConnectClientFactory();
+ var client = TryConnect(factory).Result;
+
+ var outputPath = o.OutputPath;
+ if (string.IsNullOrEmpty(outputPath))
+ outputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "Flight logs");
+
+
+ Directory.CreateDirectory(outputPath);
+ var loggers = new List();
+ AddLogger(loggers, o.OutputFormat, outputPath);
+
+ if (o.OutputFormat2 != o.OutputFormat)
+ AddLogger(loggers, o.OutputFormat2, outputPath);
+
+
+ var lastTime = DateTime.MinValue;
+ var lastData = new PositionData
+ {
+ Latitude = 0,
+ Longitude = 0,
+ Altitude = 0,
+ Airspeed = 0
+ };
+
+
+ client.AddDefinition(data =>
+ {
+ // 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 < o.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;
+ var time = now - lastTime;
+ if (time < intervalTime)
+ return;
+
+ VerboseLog("Logging position, elapsed time: " + time.TotalSeconds + ", distance: " + distanceMeters);
+
+ lastTime = now;
+ lastData = data;
+
+ // ReSharper disable once AccessToDisposedClosure - covered by disposing the client first
+ loggers.ForEach(logger =>
+ logger.LogPosition(now, new FlightPosition
+ {
+ Latitude = data.Latitude,
+ Longitude = data.Longitude,
+ Altitude = data.Altitude,
+ Airspeed = data.Airspeed
+ }));
+ });
+
+
+ var stopEvent = new ManualResetEventSlim(false);
+ Console.CancelKeyPress += (sender, args) =>
+ {
+ stopEvent.Set();
+ args.Cancel = true;
+ };
+
+ Console.WriteLine("Flight log active, press Ctrl-C to stop");
+ Console.WriteLine("Output path: " + outputPath);
+ stopEvent.Wait(Timeout.Infinite);
+
+ Console.WriteLine("Closing...");
+ client.Dispose();
+ loggers.ForEach(logger => logger.Dispose());
+
+ if (!Debugger.IsAttached)
+ return;
+
+ Console.WriteLine("Press any Enter key to continue");
+ Console.ReadLine();
+ }
+
+
+ private static void AddLogger(ICollection loggers, OutputFormat outputFormat, string outputPath)
+ {
+ switch (outputFormat)
+ {
+ case OutputFormat.CSV:
+ loggers.Add(new CSVFlightLogger(outputPath));
+ break;
+
+ case OutputFormat.KML:
+ loggers.Add(new KMLFlightLogger(outputPath, TimeSpan.FromSeconds(5)));
+ break;
+
+ case OutputFormat.None:
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+
+ private static async Task TryConnect(ISimConnectClientFactory factory)
+ {
+ while (true)
+ {
+ Console.WriteLine("Attempting to connect to SimConnect...");
+
+ var client = await factory.TryConnect("FS Flight Logger");
+ if (client != null)
+ {
+ Console.WriteLine("Success!");
+ return client;
+ }
+
+ Console.WriteLine("Failed to connect, retrying in 5 seconds");
+ await Task.Delay(TimeSpan.FromSeconds(5));
+ }
+ }
+ }
+}
diff --git a/FSFlightLoggerCmd/Properties/AssemblyInfo.cs b/FSFlightLoggerCmd/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a4c71de
--- /dev/null
+++ b/FSFlightLoggerCmd/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("FSFlightLoggerCmd")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("FSFlightLoggerCmd")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("767c7eaa-9230-4dee-89fa-9699288c831b")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/FSFlightLoggerCmd/packages.config b/FSFlightLoggerCmd/packages.config
new file mode 100644
index 0000000..3eb2507
--- /dev/null
+++ b/FSFlightLoggerCmd/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/FlightLoggerLib/Concrete/CSVFlightLogger.cs b/FlightLoggerLib/Concrete/CSVFlightLogger.cs
new file mode 100644
index 0000000..f564c3c
--- /dev/null
+++ b/FlightLoggerLib/Concrete/CSVFlightLogger.cs
@@ -0,0 +1,71 @@
+using CsvHelper;
+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using CsvHelper.Configuration;
+using CsvHelper.Configuration.Attributes;
+
+namespace FlightLoggerLib.Concrete
+{
+ public class CSVFlightLogger : IFlightLogger
+ {
+ private readonly CsvWriter output;
+
+
+ public CSVFlightLogger(string path)
+ {
+ var filename = Path.Combine(path, DateTime.Now.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
+ });
+ }
+
+
+ public void Dispose()
+ {
+ output?.Dispose();
+ }
+
+
+ public async Task LogPosition(DateTime eventTime, FlightPosition position)
+ {
+ var record = new OutputRecord
+ {
+ Time = eventTime,
+ Latitude = position.Latitude,
+ Longitude = position.Longitude,
+ Altitude = position.Altitude,
+ Airspeed = position.Airspeed
+ };
+
+ await output.WriteRecordsAsync(Enumerable.Repeat(record, 1));
+ await output.FlushAsync();
+ }
+
+
+
+ protected class OutputRecord
+ {
+ [Index(0)]
+ public DateTime Time { get; set; }
+
+ [Index(1)]
+ public float Latitude { get; set; }
+
+ [Index(2)]
+ public float Longitude { get; set; }
+
+ [Index(3)]
+ public float Altitude { get; set; }
+
+ [Index(4)]
+ public float Airspeed { get; set; }
+ }
+ }
+}
diff --git a/FlightLoggerLib/Concrete/KMLFlightLogger.cs b/FlightLoggerLib/Concrete/KMLFlightLogger.cs
new file mode 100644
index 0000000..483bdc1
--- /dev/null
+++ b/FlightLoggerLib/Concrete/KMLFlightLogger.cs
@@ -0,0 +1,208 @@
+using System;
+using System.IO;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using SharpKml.Base;
+using SharpKml.Dom;
+
+namespace FlightLoggerLib.Concrete
+{
+ public class KMLFlightLogger : IFlightLogger
+ {
+ private readonly string filename;
+ private readonly System.TimeSpan flushInterval;
+ private readonly Document output;
+ private readonly Folder rootFolder;
+ private readonly LineString positionPath;
+
+ private DateTime lastFlush = DateTime.MinValue;
+ private Vector lastPosition;
+ private float lastSpeed;
+ private DateTime lastPointDate = DateTime.MinValue;
+
+
+ public KMLFlightLogger(string path, System.TimeSpan flushInterval)
+ {
+ var dateString = DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss");
+ filename = Path.Combine(path, dateString + ".kml");
+ this.flushInterval = flushInterval;
+
+
+ // Create folder
+ rootFolder = new Folder
+ {
+ Name = dateString,
+ Open = true
+ };
+
+
+ // Create flight path line and placemark
+ positionPath = new LineString
+ {
+ Tessellate = false,
+ AltitudeMode = AltitudeMode.Absolute,
+ Coordinates = new CoordinateCollection()
+ };
+
+ var positionPlacemark = new Placemark
+ {
+ Name = "Flight path",
+ StyleUrl = new Uri("#flightpath", UriKind.Relative),
+ Geometry = positionPath
+ };
+
+ rootFolder.AddFeature(positionPlacemark);
+
+ output = new Document();
+
+ AddFlightPathStyleMap();
+
+ var paddleHotspot = new Hotspot { X = 31, XUnits = Unit.Pixel, Y = 1, YUnits = Unit.Pixel };
+ AddIconStyleMap("start", 1.1, "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png", "http://maps.google.com/mapfiles/kml/paddle/grn-circle-lv.png", paddleHotspot);
+ AddIconStyleMap("end", 1.1, "http://maps.google.com/mapfiles/kml/paddle/red-square.png", "http://maps.google.com/mapfiles/kml/paddle/red-square-lv.png", paddleHotspot);
+ AddIconStyleMap("fullstop", 1.1, "http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png", null, paddleHotspot);
+
+ 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()
+ {
+ var styleMap = new StyleMapCollection { Id = "flightpath" };
+ styleMap.Add(new Pair { State = StyleState.Normal, StyleUrl = new Uri("flightpath-normal", UriKind.Relative) });
+ styleMap.Add(new Pair { State = StyleState.Highlight, StyleUrl = new Uri("flightpath-normal", UriKind.Relative) });
+
+ var styleNormal = new Style
+ {
+ Id = "flightpath-normal",
+ Line = new LineStyle
+ {
+ Color = new Color32(255, 10, 10, 138),
+ Width = 5
+ }
+ };
+
+ output.AddStyle(styleMap);
+ output.AddStyle(styleNormal);
+ }
+
+
+ protected void AddIconStyleMap(string id, double scale, string iconUrl, string listUrl, Hotspot iconHotspot = null)
+ {
+ var styleMap = new StyleMapCollection { Id = id };
+ styleMap.Add(new Pair { State = StyleState.Normal, StyleUrl = new Uri(id + "-normal", UriKind.Relative) });
+ styleMap.Add(new Pair { State = StyleState.Highlight, StyleUrl = new Uri(id + "-normal", UriKind.Relative) });
+
+ var styleNormal = new Style
+ {
+ Id = id + "-normal",
+ Icon = new IconStyle
+ {
+ Scale = scale,
+ Icon = new IconStyle.IconLink(new Uri(iconUrl, UriKind.Absolute)),
+ Hotspot = iconHotspot
+ },
+ };
+
+ if (!string.IsNullOrEmpty(listUrl))
+ {
+ styleNormal.List = new ListStyle();
+ styleNormal.List.AddItemIcon(new ItemIcon
+ {
+ Href = new Uri(listUrl, UriKind.Absolute)
+ });
+ }
+
+
+ output.AddStyle(styleMap);
+ output.AddStyle(styleNormal);
+ }
+
+
+ protected string GetPointDate()
+ {
+ var now = DateTime.Now;
+
+ // If the date hasn't changed since the last point label, just return the time
+ if (now.Date == lastPointDate)
+ return now.ToString("T");
+
+ lastPointDate = now.Date;
+ return now.ToString("F");
+ }
+
+
+ protected void AddPoint(Vector coordinate, string label, string styleMapId, bool includeTimestamp = true)
+ {
+ var point = new Point { Coordinate = coordinate, AltitudeMode = AltitudeMode.Absolute };
+
+ var placemark = new Placemark
+ {
+ Name = includeTimestamp ? $"{label} ({GetPointDate()})" : label,
+ StyleUrl = new Uri("#" + styleMapId, UriKind.Relative),
+ Geometry = point,
+ Visibility = true
+ };
+
+ rootFolder.AddFeature(placemark);
+ }
+
+
+ private const float MetersPerFoot = 0.3048f;
+
+
+ public async Task LogPosition(DateTime eventTime, FlightPosition position)
+ {
+ var altitudeMeters = position.Altitude * MetersPerFoot;
+ var coordinate = new Vector(position.Latitude, position.Longitude, altitudeMeters);
+
+ if (lastPosition == null)
+ AddPoint(coordinate, "Start", "start");
+
+ if (lastSpeed > 0 && position.Airspeed == 0)
+ AddPoint(coordinate, "Full stop", "fullstop");
+
+ lastPosition = coordinate;
+ lastSpeed = position.Airspeed;
+
+ positionPath.Coordinates.Add(coordinate);
+
+ await AutoFlush();
+ }
+
+
+ // TODO log events, engine stop etc.
+ }
+}
diff --git a/FlightLoggerLib/FlightLoggerLib.csproj b/FlightLoggerLib/FlightLoggerLib.csproj
new file mode 100644
index 0000000..74a5da7
--- /dev/null
+++ b/FlightLoggerLib/FlightLoggerLib.csproj
@@ -0,0 +1,95 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {D85BCC97-F653-4286-98D9-073A33A55857}
+ Library
+ Properties
+ FlightLoggerLib
+ FlightLoggerLib
+ v4.7.2
+ 512
+ true
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ 7.3
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ true
+ bin\x64\Debug\
+ x64
+ 7.3
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ 7.3
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ true
+ bin\x86\Debug\
+ x86
+ 7.3
+ MinimumRecommendedRules.ruleset
+ true
+
+
+
+ ..\packages\CsvHelper.15.0.5\lib\net47\CsvHelper.dll
+
+
+ ..\packages\DotNetZip.Reduced.1.9.1.8\lib\net20\Ionic.Zip.Reduced.dll
+
+
+ ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll
+
+
+ ..\packages\SharpKml.Core.5.1.3\lib\net45\SharpKml.Core.dll
+
+
+
+
+
+ ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
+
+
+ ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FlightLoggerLib/IFlightLogger.cs b/FlightLoggerLib/IFlightLogger.cs
new file mode 100644
index 0000000..9ad42c3
--- /dev/null
+++ b/FlightLoggerLib/IFlightLogger.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Threading.Tasks;
+
+namespace FlightLoggerLib
+{
+ public class FlightPosition
+ {
+ public float Latitude { get; set; }
+ public float Longitude { get; set; }
+ public float Altitude { get; set; }
+ public float Airspeed { get; set; }
+ }
+
+
+
+ public interface IFlightLogger : IDisposable
+ {
+ Task LogPosition(DateTime eventTime, FlightPosition position);
+ //void LogEvent
+ }
+}
diff --git a/FlightLoggerLib/Properties/AssemblyInfo.cs b/FlightLoggerLib/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..8acdc0d
--- /dev/null
+++ b/FlightLoggerLib/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("FlightLoggerLib")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("FlightLoggerLib")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d85bcc97-f653-4286-98d9-073a33a55857")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/FlightLoggerLib/app.config b/FlightLoggerLib/app.config
new file mode 100644
index 0000000..d0d658a
--- /dev/null
+++ b/FlightLoggerLib/app.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FlightLoggerLib/packages.config b/FlightLoggerLib/packages.config
new file mode 100644
index 0000000..caf7147
--- /dev/null
+++ b/FlightLoggerLib/packages.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..406170d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# FSFlightLogger
+
+Log flight paths and other information to CSV or Google Earth KML files.
+
+Works with SimConnect compatible simulators, including Microsoft Flight Simulator X and 2020 (not 2004 and earlier) and Lockheed Martin's Prepar3D.
+
+32-bits builds uses the SimConnect clients up to Flight Simulator X, which will work with Flight Simulator 2020. 64-bits builds can only work with the Flight Simulator 2020 SimConnect client and is not compatible with earlier Flight Simualtors.
+
+
+This project is in it's infancy. A working command-line version is implemented as a proof of concept, a proper UI with more features is in development.
+It also includes a (far from incomplete) C# wrapper for the native SimConnect DLL instead of using the default managed wrapper, as it allowed for easier switching between versions.
\ No newline at end of file
diff --git a/SimConnect/Attribute/SimConnectVariableAttribute.cs b/SimConnect/Attribute/SimConnectVariableAttribute.cs
new file mode 100644
index 0000000..fa672b0
--- /dev/null
+++ b/SimConnect/Attribute/SimConnectVariableAttribute.cs
@@ -0,0 +1,60 @@
+namespace SimConnect.Attribute
+{
+ ///
+ /// Indicates the property should be registered as a SimConnect variable in the definition to receive updates.
+ ///
+ public class SimConnectVariableAttribute : System.Attribute
+ {
+ ///
+ /// The name of the SimConnect variable
+ ///
+ public string VariableName { get; }
+
+ ///
+ /// The SimConnect data type. If null, the SimConnect data type will be determined from the .NET type.
+ ///
+ public SimConnectDataType? DataType { get; }
+
+ ///
+ /// The requested units for the value. See the SimConnect documentation for available units
+ ///
+ public string UnitsName { get; }
+
+ ///
+ /// For integer and floating point types, determines how much the value must change before an update is sent
+ ///
+ public float Epsilon { get; }
+
+
+ ///
+ /// Indicates the property should be registered as a SimConnect variable in the definition to receive updates.
+ ///
+ /// The name of the SimConnect variable
+ /// The SimConnect data type. If null, the SimConnect data type will be determined from the .NET type.
+ /// The requested units for the value. See the SimConnect documentation for available units
+ /// For integer and floating point types, determines how much the value must change before an update is sent
+ public SimConnectVariableAttribute(string variableName, SimConnectDataType dataType, string unitsName = "", float epsilon = 0)
+ {
+ VariableName = variableName;
+ DataType = dataType;
+ UnitsName = unitsName;
+ Epsilon = epsilon;
+ }
+
+
+ ///
+ /// Indicates the property should be registered as a SimConnect variable in the definition to receive updates.
+ ///
+ /// The name of the SimConnect variable
+ /// The requested units for the value. See the SimConnect documentation for available units
+ /// For integer and floating point types, determines how much the value must change before an update is sent
+ public SimConnectVariableAttribute(string variableName, string unitsName = "", float epsilon = 0)
+ {
+ VariableName = variableName;
+ DataType = null;
+ UnitsName = unitsName;
+ Epsilon = epsilon;
+ }
+
+ }
+}
diff --git a/SimConnect/Concrete/DefaultSimConnectClient.cs b/SimConnect/Concrete/DefaultSimConnectClient.cs
new file mode 100644
index 0000000..671c6ed
--- /dev/null
+++ b/SimConnect/Concrete/DefaultSimConnectClient.cs
@@ -0,0 +1,272 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
+
+namespace SimConnect.Concrete
+{
+ ///
+ /// Default implementation of ISimConnectClient. Compatible with Flight Simulator 2020, X Stream Edition, X and Prepar3D.
+ /// 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.
+ ///
+ public class DefaultSimConnectClient : ISimConnectClient, IDisposable
+ {
+ private readonly ISimConnectLibrary simConnectLibrary;
+ private SimConnectWorker worker;
+
+ private uint nextDefinitionID = 1;
+
+
+ ///
+ /// Creates an instance of DefaultSimConnectClient.
+ ///
+ /// The low-level SimConnect library interface to use
+ public DefaultSimConnectClient(ISimConnectLibrary simConnectLibrary)
+ {
+ this.simConnectLibrary = simConnectLibrary;
+ }
+
+
+ ///
+ public void Dispose()
+ {
+ worker?.Close().Wait();
+ simConnectLibrary?.Dispose();
+ }
+
+ ///
+ /// Attempts to open a connection to the SimConnect server
+ ///
+ /// The application name passed to the SimConnect server.
+ ///
+ public async Task TryOpen(string appName)
+ {
+ if (worker != null)
+ await worker.Close();
+
+ worker = new SimConnectWorker(simConnectLibrary, appName);
+ return await worker.Open();
+ }
+
+
+ ///
+ public void AttachObserver(ISimConnectClientObserver observer)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ ///
+ public IDisposable AddDefinition(SimConnectDataHandlerAction onData) where T : class
+ {
+ if (worker == null)
+ throw new InvalidOperationException("TryOpen must be called first");
+
+ var definition = new SimConnectDefinition(typeof(T));
+ void HandleData(Stream stream)
+ {
+ var data = definition.ParseData(stream);
+ onData((T)data);
+ }
+
+ var definitionRegistration = new SimConnectDefinitionRegistration(nextDefinitionID, definition, HandleData, worker);
+ nextDefinitionID++;
+
+ return definitionRegistration;
+ }
+
+
+ private class SimConnectDefinitionRegistration : IDisposable where T : class
+ {
+ private readonly uint definitionID;
+ private readonly SimConnectWorker worker;
+
+
+ public SimConnectDefinitionRegistration(uint definitionID, SimConnectDefinition definition, Action onData, SimConnectWorker worker)
+ {
+ this.definitionID = definitionID;
+ this.worker = worker;
+
+ worker.RegisterDefinition(definitionID, definition, onData);
+ }
+
+
+ public void Dispose()
+ {
+ worker.UnregisterDefinition(definitionID);
+ }
+ }
+
+
+ private class SimConnectWorker
+ {
+ private readonly ISimConnectLibrary simConnectLibrary;
+ private readonly string appName;
+
+ private Task workerTask;
+
+ private readonly AutoResetEvent workerPulse = new AutoResetEvent(false);
+ private readonly object workerLock = new object();
+ private volatile bool closed;
+ private readonly Queue> workQueue = new Queue>();
+
+ private readonly TaskCompletionSource openResult = new TaskCompletionSource();
+ private readonly ConcurrentDictionary> definitionDataHandler = new ConcurrentDictionary>();
+
+
+ public SimConnectWorker(ISimConnectLibrary simConnectLibrary, string appName)
+ {
+ this.simConnectLibrary = simConnectLibrary;
+ this.appName = appName;
+ }
+
+
+ public async Task Open()
+ {
+ if (workerTask == null)
+ workerTask = Task.Run(RunInBackground);
+
+ return await openResult.Task;
+ }
+
+
+ public async Task Close()
+ {
+ closed = true;
+
+ workerPulse.Set();
+ await workerTask;
+ }
+
+
+ public void RegisterDefinition(uint definitionID, SimConnectDefinition definition, Action onData)
+ {
+ Enqueue(hSimConnect =>
+ {
+ foreach (var variable in definition.Variables)
+ {
+ simConnectLibrary.SimConnect_AddToDataDefinition(hSimConnect, definitionID, variable.VariableName, variable.UnitsName,
+ variable.DataType, variable.Epsilon);
+ }
+
+ definitionDataHandler.AddOrUpdate(definitionID, onData, (key, value) => onData);
+ simConnectLibrary.SimConnect_RequestDataOnSimObject(hSimConnect, definitionID, definitionID, 0, SimConnectPeriod.SimFrame, 1);
+ });
+ }
+
+
+ public void UnregisterDefinition(uint definitionID)
+ {
+ Enqueue(hSimConnect =>
+ {
+ definitionDataHandler.TryRemove(definitionID, out var unused);
+ simConnectLibrary.SimConnect_ClearDataDefinition(hSimConnect, definitionID);
+ });
+ }
+
+
+ private void Enqueue(Action work)
+ {
+ lock(workerLock)
+ {
+ workQueue.Enqueue(work);
+ }
+
+ workerPulse.Set();
+ }
+
+
+ private void RunInBackground()
+ {
+ var dataEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
+
+ var errorCode = simConnectLibrary.SimConnect_Open(out var simConnectHandle, appName, IntPtr.Zero, 0, dataEvent.SafeWaitHandle.DangerousGetHandle(), 0);
+ openResult.TrySetResult(errorCode == 0);
+
+ if (errorCode != 0)
+ return;
+
+
+ while(!closed)
+ {
+ switch (WaitHandle.WaitAny(new WaitHandle[] { workerPulse, dataEvent }))
+ {
+ case 0:
+ HandleWorkQueue(simConnectHandle);
+ break;
+
+ case 1:
+ HandleData(ref simConnectHandle);
+ break;
+ }
+ }
+
+ if (simConnectHandle != IntPtr.Zero)
+ simConnectLibrary.SimConnect_Close(simConnectHandle);
+ }
+
+
+ private void HandleWorkQueue(IntPtr simConnectHandle)
+ {
+ while (!closed)
+ {
+ Action work;
+
+ lock(workerLock)
+ {
+ work = workQueue.Count > 0 ? workQueue.Dequeue() : null;
+ }
+
+ if (work == null)
+ break;
+
+ work(simConnectHandle);
+ }
+ }
+
+
+ private void HandleData(ref IntPtr simConnectHandle)
+ {
+ while (!closed && simConnectLibrary.SimConnect_GetNextDispatch(simConnectHandle, out var dataPtr, out var dataSize) == 0)
+ {
+ var recv = Marshal.PtrToStructure(dataPtr);
+
+ // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
+ switch ((SimConnectRecvID) recv.dwID)
+ {
+ case SimConnectRecvID.Exception:
+ var recvException = Marshal.PtrToStructure(dataPtr);
+ if (recvException.dwException == 0)
+ break;
+
+ break;
+
+ case SimConnectRecvID.SimobjectData:
+ case SimConnectRecvID.SimobjectDataByType:
+ var recvSimobjectData = Marshal.PtrToStructure(dataPtr);
+ if (!definitionDataHandler.TryGetValue((uint)recvSimobjectData.dwDefineID, out var dataHandler))
+ break;
+
+ unsafe
+ {
+ var streamOffset = Marshal.OffsetOf("dwData").ToInt32();
+ var stream = new UnmanagedMemoryStream((byte*)IntPtr.Add(dataPtr, streamOffset).ToPointer(), (long)dataSize - streamOffset);
+ dataHandler(stream);
+ }
+ break;
+
+ case SimConnectRecvID.Quit:
+ simConnectHandle = IntPtr.Zero;
+ closed = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/SimConnect/Concrete/SimConnectClientFactory.cs b/SimConnect/Concrete/SimConnectClientFactory.cs
new file mode 100644
index 0000000..054cdea
--- /dev/null
+++ b/SimConnect/Concrete/SimConnectClientFactory.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Threading.Tasks;
+using SimConnect.Unmanaged;
+
+namespace SimConnect.Concrete
+{
+ ///
+ /// Default implementation of ISimConnectClientFactory
+ ///
+ public class SimConnectClientFactory : ISimConnectClientFactory
+ {
+ ///
+ public async Task TryConnect(string appName)
+ {
+ // FS 2020 SimConnect.dll is 64-bits, the others are 32-bits
+ if (Environment.Is64BitProcess)
+ return await TryDefaultClient(appName, "FS2020-SimConnect.dll");
+
+ // This order prevents a version mismatch, but perhaps an option to explicitly set the version might be nice as well
+ return await TryDefaultClient(appName, "FSX-SimConnect.dll")
+ ?? await TryDefaultClient(appName, "FSXSP2-SimConnect.dll")
+ ?? await TryDefaultClient(appName, "FSX-SE-SimConnect.dll");
+ }
+
+
+ private static async Task TryDefaultClient(string appName, string libraryFilename)
+ {
+ var library = new SimConnectDLLLibrary(libraryFilename);
+ var client = new DefaultSimConnectClient(library);
+
+ return await client.TryOpen(appName) ? client : null;
+ }
+ }
+}
diff --git a/SimConnect/Concrete/SimConnectDefinition.cs b/SimConnect/Concrete/SimConnectDefinition.cs
new file mode 100644
index 0000000..b001a43
--- /dev/null
+++ b/SimConnect/Concrete/SimConnectDefinition.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using SimConnect.Attribute;
+
+namespace SimConnect.Concrete
+{
+ ///
+ /// Defines a SimConnect variable.
+ ///
+ public struct SimConnectVariable
+ {
+ ///
+ /// The name of the SimConnect variable
+ ///
+ public string VariableName;
+
+ ///
+ /// The SimConnect data type. If null, the SimConnect data type will be determined from the .NET type.
+ ///
+ public SimConnectDataType DataType;
+
+ ///
+ /// The requested units for the value. See the SimConnect documentation for available units
+ ///
+ public string UnitsName;
+
+ ///
+ /// For integer and floating point types, determines how much the value must change before an update is sent
+ ///
+ public float Epsilon;
+ };
+
+
+ ///
+ /// Creates a SimConnect definition from properties annotated with the SimConnectVariable attribute.
+ ///
+ public class SimConnectDefinition
+ {
+ private readonly Type type;
+
+ private delegate void SetterProc(object instance, BinaryReader data);
+
+ private struct SimConnectVariableProperty
+ {
+ public SimConnectVariable Variable;
+ public SetterProc Setter;
+ }
+
+ ///
+ /// Provides access to the parsed variables.
+ ///
+ public IEnumerable Variables => variables.Select(v => v.Variable);
+
+ private readonly List variables;
+
+
+ ///
+ /// Creates an instance of a SimConnectDefinition based on the provided class.
+ ///
+ /// The class type used for the definition
+ public SimConnectDefinition(Type type)
+ {
+ if (!type.IsClass)
+ throw new InvalidOperationException($"{type.FullName} is not a class type");
+
+ this.type = type;
+ variables = ParseVariables(type);
+
+ if (variables.Count == 0)
+ throw new InvalidOperationException($"At least one property of {type.FullName} should be annotated with the SimConnectVariable attribute");
+ }
+
+
+ ///
+ /// Parses the SimConnect data stream to an object according to this definition.
+ ///
+ /// The SimConnect data stream
+ /// An instance of the type for this definition
+ public object ParseData(Stream data)
+ {
+ var reader = new BinaryReader(data);
+ var instance = Activator.CreateInstance(type);
+
+ foreach (var variable in variables)
+ variable.Setter(instance, reader);
+
+ return instance;
+ }
+
+
+ private static List ParseVariables(IReflect type)
+ {
+ var result = new List();
+
+ foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
+ .Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property))
+ {
+ var variableAttribute = member.GetCustomAttribute();
+ if (variableAttribute == null)
+ continue;
+
+
+ var dataType = variableAttribute.DataType;
+ var setter = GetSetter(member, ref dataType);
+
+ if (!dataType.HasValue)
+ throw new InvalidOperationException($"DataType could not be determined for member {member.Name}");
+
+ result.Add(new SimConnectVariableProperty
+ {
+ Variable = new SimConnectVariable
+ {
+ VariableName = variableAttribute.VariableName,
+ DataType = dataType.Value,
+ UnitsName = variableAttribute.UnitsName,
+ Epsilon = variableAttribute.Epsilon
+ },
+ Setter = setter
+ });
+ }
+
+ return result;
+ }
+
+
+ private readonly struct SimConnectTypeMapping
+ {
+ public readonly Type Type;
+ public readonly SimConnectDataType SimConnectDataType;
+ public readonly bool IsDefaultForTypeForType;
+ public readonly Func Converter;
+
+ public SimConnectTypeMapping(Type type, SimConnectDataType simConnectDataType, bool isDefaultForType, Func converter)
+ {
+ Type = type;
+ SimConnectDataType = simConnectDataType;
+ IsDefaultForTypeForType = isDefaultForType;
+ Converter = converter;
+ }
+ }
+
+
+ private static readonly List SimConnectTypeMappings = new List
+ {
+ { new SimConnectTypeMapping(typeof(double), SimConnectDataType.Float64, true, reader => reader.ReadDouble()) },
+ { new SimConnectTypeMapping(typeof(float), SimConnectDataType.Float32, true, reader => reader.ReadSingle()) },
+ { new SimConnectTypeMapping(typeof(int), SimConnectDataType.Int32, true, reader => reader.ReadInt32()) },
+ { new SimConnectTypeMapping(typeof(uint), SimConnectDataType.Int32, true, reader => reader.ReadUInt32()) },
+ { new SimConnectTypeMapping(typeof(long), SimConnectDataType.Int64, true, reader => reader.ReadInt64()) },
+ { new SimConnectTypeMapping(typeof(ulong), SimConnectDataType.Int64, true, reader => reader.ReadUInt64()) }
+ };
+
+
+ private static SetterProc GetSetter(MemberInfo member, ref SimConnectDataType? dataType)
+ {
+ Type valueType;
+ Action