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 valueSetter; + + if (member.MemberType == MemberTypes.Field) + { + var fieldInfo = (FieldInfo)member; + valueType = fieldInfo.FieldType; + valueSetter = (instance, data) => fieldInfo.SetValue(instance, data); + + } + else + { + var propertyInfo = (PropertyInfo)member; + valueType = propertyInfo.PropertyType; + valueSetter = (instance, data) => propertyInfo.SetValue(instance, data); + } + + + foreach (var mapping in SimConnectTypeMappings.Where(mapping => mapping.Type == valueType)) + { + if (dataType.HasValue && mapping.SimConnectDataType != dataType) + continue; + + if (!dataType.HasValue && !mapping.IsDefaultForTypeForType) + continue; + + if (!dataType.HasValue) + dataType = mapping.SimConnectDataType; + + return (instance, reader) => + { + valueSetter(instance, mapping.Converter(reader)); + }; + } + + throw new InvalidOperationException($"No mapping exists for type {valueType.FullName} with SimConnect DataType {dataType} for member {member.Name}"); + } + } +} diff --git a/SimConnect/FS2020-SimConnect.dll b/SimConnect/FS2020-SimConnect.dll new file mode 100644 index 0000000..87b29a2 Binary files /dev/null and b/SimConnect/FS2020-SimConnect.dll differ diff --git a/SimConnect/FSX-SE-SimConnect.dll b/SimConnect/FSX-SE-SimConnect.dll new file mode 100644 index 0000000..afa03e9 Binary files /dev/null and b/SimConnect/FSX-SE-SimConnect.dll differ diff --git a/SimConnect/FSX-SimConnect.dll b/SimConnect/FSX-SimConnect.dll new file mode 100644 index 0000000..c2f1ebe Binary files /dev/null and b/SimConnect/FSX-SimConnect.dll differ diff --git a/SimConnect/FSXSP2-SimConnect.dll b/SimConnect/FSXSP2-SimConnect.dll new file mode 100644 index 0000000..a2bf93e Binary files /dev/null and b/SimConnect/FSXSP2-SimConnect.dll differ diff --git a/SimConnect/ISimConnectClient.cs b/SimConnect/ISimConnectClient.cs new file mode 100644 index 0000000..a521632 --- /dev/null +++ b/SimConnect/ISimConnectClient.cs @@ -0,0 +1,46 @@ +using System; + +namespace SimConnect +{ + /// + /// Called when new data arrives from the SimConnect server. + /// + /// An instance of the data class as passed to AddDefinition containing the variable values + /// The data class as passed to AddDefinition + public delegate void SimConnectDataHandlerAction(T data) where T : class; + + + /// + /// Gets notified of changes to the SimConnect state. + /// + public interface ISimConnectClientObserver + { + /// + /// Gets called when the SimConnect connection is lost. The client will not receive further notifications, + /// a new connection should be attempted if desired. + /// + void OnQuit(); + } + + + /// + /// Provides access to the SimConnect library. + /// + public interface ISimConnectClient : IDisposable + { + /// + /// Attaches the specified observer to receive status notifications. + /// + /// The observer to receive status notifications + void AttachObserver(ISimConnectClientObserver observer); + + + /// + /// Registers a definition to receive updates from the SimConnect server. + /// + /// A callback method which is called whenever a data update is received + /// A class defining the variables to monitor annotated using the SimConnectVariable attribute + /// An IDisposable which can be disposed to unregister the definition. Dispose is not required to be called when the client is disconnected, but will not throw an exception. + IDisposable AddDefinition(SimConnectDataHandlerAction onData) where T : class; + } +} diff --git a/SimConnect/ISimConnectClientFactory.cs b/SimConnect/ISimConnectClientFactory.cs new file mode 100644 index 0000000..e646dad --- /dev/null +++ b/SimConnect/ISimConnectClientFactory.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace SimConnect +{ + /// + /// Provides a factory for creating a SimConnect client instance. + /// + public interface ISimConnectClientFactory + { + /// + /// Tries to connect to any of the compatible running SimConnect servers. + /// + /// The application name passed to the SimConnect server. + /// A client interface if succesful or nil if no connection could be made. + Task TryConnect(string appName); + } +} diff --git a/SimConnect/ISimConnectLibrary.cs b/SimConnect/ISimConnectLibrary.cs new file mode 100644 index 0000000..8b9dbd2 --- /dev/null +++ b/SimConnect/ISimConnectLibrary.cs @@ -0,0 +1,186 @@ +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace SimConnect +{ + /// + /// Corresponds to SIMCONNECT_DATAType. + /// + public enum SimConnectDataType + { + /// + /// invalid data Type + /// + Invalid = 0, + + /// + /// 32-bit integer number + /// + Int32, + + /// + /// 64-bit integer number + /// + Int64, + + /// + /// 32-bit floating-point number (Single) + /// + Float32, + + /// + /// 64-bit floating-point number (Double) + /// + Float64, + + /// + /// 8-byte String + /// + String8, + + /// + /// 32-byte String + /// + String32, + + /// + /// 64-byte String + /// + String64, + + /// + /// 128-byte String + /// + String128, + + /// + /// 256-byte String + /// + String256, + + /// + /// 260-byte String + /// + String260, + + /// + /// variable-length String + /// + StringV, + + /// + /// see SIMCONNECT_DATA_INITPOSITION + /// + InitPosition, + + /// + /// see SIMCONNECT_DATA_MARKERSTATE + /// + MarkerState, + + /// + /// see SIMCONNECT_DATA_WAYPOINT + /// + Waypoint, + + /// + /// see SIMCONNECT_DATA_LATLONALT + /// + LatLonAlt, + + /// + /// see SIMCONNECT_DATA_XYZ + /// + XYZ + } + + + // see SimConnect documentation + #pragma warning disable 1591 + public enum SimConnectPeriod + { + Never = 0, + Once, + VisualFrame, + SimFrame, + Second + }; + + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct SimConnectRecv + { + public uint dwSize; + public uint dwVersion; + public uint dwID; + } + + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct SimConnectRecvException + { + public SimConnectRecv Recv; + + public uint dwException; + public uint dwSendID; + public uint dwIndex; + } + + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct SimConnectRecvSimobjectData + { + public SimConnectRecv Recv; + + public uint dwRequestID; + public uint dwObjectID; + public uint dwDefineID; + public uint dwFlags; // SIMCONNECT_DATA_REQUEST_FLAG + public uint dwentrynumber; + // if multiple objects returned, this is number out of . + public uint dwoutof; // note: starts with 1, not 0. + public uint dwDefineCount; // data count (number of datums, *not* byte count) + public uint dwData; // data begins here, dwDefineCount data items + } + + + /// + /// Provides a low-level interface to a compatible SimConnect.dll. + /// + public interface ISimConnectLibrary : IDisposable + { + uint SimConnect_Open(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex); + uint SimConnect_Close(IntPtr hSimConnect); + + uint SimConnect_AddToDataDefinition(IntPtr hSimConnect, uint defineID, string datumName, string unitsName, SimConnectDataType datumType = SimConnectDataType.Float64, float epsilon = 0, uint datumID = uint.MaxValue); + uint SimConnect_ClearDataDefinition(IntPtr hSimConnect, uint defineID); + + uint SimConnect_RequestDataOnSimObject(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0); + uint SimConnect_GetNextDispatch(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData); + } + + + + public enum SimConnectRecvID + { + Null = 0, + Exception, + Open, + Quit, + Event, + EventObjectAddRemove, + EventFilename, + EventFrame, + SimobjectData, + SimobjectDataByType, + WeatherObservation, + CloudState, + AssignedObjectID, + ReservedKey, + CustomAction, + SystemState, + ClientData + }; + #pragma warning restore 1591 +} diff --git a/SimConnect/Lib/LatLon.cs b/SimConnect/Lib/LatLon.cs new file mode 100644 index 0000000..7fc565d --- /dev/null +++ b/SimConnect/Lib/LatLon.cs @@ -0,0 +1,40 @@ +using System; + +namespace SimConnect.Lib +{ + /// + /// Provides helpers methods for latitude / longitude calculations. + /// + public static class LatLon + { + /// + /// Defines the approximate radius of the earth in kilometers. + /// + public const double EarthRadiusKm = 6378.137; + + + // Source: https://stackoverflow.com/questions/639695/how-to-convert-latitude-or-longitude-to-meters + /// + /// Calculates the distance between two coordinates. + /// + /// Latitude of point 1 in degrees + /// Longitude of point 1 in degrees + /// Latitude of point 2 in degrees + /// Longitude of point 2 in degrees + /// + public static double DistanceBetweenInMeters(float lat1, float lon1, float lat2, float lon2) + { + var distanceLat = lat2 * Math.PI / 180 - lat1 * Math.PI / 180; + var distanceLon = lon2 * Math.PI / 180 - lon1 * Math.PI / 180; + + var a = Math.Sin(distanceLat / 2) * Math.Sin(distanceLat / 2) + + Math.Cos(lat1 * Math.PI / 180) * Math.Cos(lat2 * Math.PI / 180) * + Math.Sin(distanceLon / 2) * Math.Sin(distanceLon / 2); + + var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); + var distanceKm = EarthRadiusKm * c; + + return distanceKm * 1000; + } + } +} diff --git a/SimConnect/Lib/UnmanagedLibrary.cs b/SimConnect/Lib/UnmanagedLibrary.cs new file mode 100644 index 0000000..8493480 --- /dev/null +++ b/SimConnect/Lib/UnmanagedLibrary.cs @@ -0,0 +1,117 @@ +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using Microsoft.Win32.SafeHandles; + +// Source: https://docs.microsoft.com/en-us/archive/blogs/jmstall/type-safe-managed-wrappers-for-kernel32getprocaddress +namespace SimConnect.Lib +{ + /// + /// Utility class to wrap an unmanaged DLL and be responsible for freeing it. + /// + /// + /// This is a managed wrapper over the native LoadLibrary, GetProcAddress, and FreeLibrary calls. + /// + public sealed class UnmanagedLibrary : IDisposable + { + /// + /// See http://msdn.microsoft.com/msdnmag/issues/05/10/Reliability/ for more about safe handles. + /// + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + public sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private SafeLibraryHandle() : base(true) { } + + /// + protected override bool ReleaseHandle() + { + return NativeMethods.FreeLibrary(handle); + } + } + + private static class NativeMethods + { + private const string SKernel = "kernel32"; + + [DllImport(SKernel, CharSet = CharSet.Auto, BestFitMapping = false, SetLastError = true)] + public static extern SafeLibraryHandle LoadLibrary(string fileName); + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [DllImport(SKernel, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FreeLibrary(IntPtr hModule); + + [DllImport(SKernel)] + public static extern IntPtr GetProcAddress(SafeLibraryHandle hModule, String procname); + } + + /// + /// Constructor to load a dll and be responible for freeing it. + /// + /// full path name of dll to load + /// if fileName can't be found + /// Throws exceptions on failure. Most common failure would be file-not-found, or + /// that the file is not a loadable image. + public UnmanagedLibrary(string fileName) + { + libraryHandle = NativeMethods.LoadLibrary(fileName); + if (!libraryHandle.IsInvalid) + return; + + var hr = Marshal.GetHRForLastWin32Error(); + Marshal.ThrowExceptionForHR(hr); + } + + /// + /// Dynamically lookup a function in the dll via kernel32!GetProcAddress. + /// + /// raw name of the function in the export table. + /// null if function is not found. Else a delegate to the unmanaged function. + /// + /// GetProcAddress results are valid as long as the dll is not yet unloaded. This + /// is very very dangerous to use since you need to ensure that the dll is not unloaded + /// until after you're done with any objects implemented by the dll. For example, if you + /// get a delegate that then gets an IUnknown implemented by this dll, + /// you can not dispose this library until that IUnknown is collected. Else, you may free + /// the library and then the CLR may call release on that IUnknown and it will crash. + public TDelegate GetUnmanagedFunction(string functionName) where TDelegate : class + { + var p = NativeMethods.GetProcAddress(libraryHandle, functionName); + + // Failure is a common case, especially for adaptive code. + if (p == IntPtr.Zero) + return null; + + var function = Marshal.GetDelegateForFunctionPointer(p, typeof(TDelegate)); + + // Ideally, we'd just make the constraint on TDelegate be + // System.Delegate, but compiler error CS0702 (constrained can't be System.Delegate) + // prevents that. So we make the constraint system.object and do the cast from object-->TDelegate. + object o = function; + + return (TDelegate)o; + } + + /// + /// Call FreeLibrary on the unmanaged dll. All function pointers + /// handed out from this class become invalid after this. + /// + /// This is very dangerous because it suddenly invalidate + /// everything retrieved from this dll. This includes any functions + /// handed out via GetProcAddress, and potentially any objects returned + /// from those functions (which may have an implemention in the + /// dll). + /// + public void Dispose() + { + if (!libraryHandle.IsClosed) + { + libraryHandle.Close(); + } + } + + // Unmanaged resource. CLR will ensure SafeHandles get freed, without requiring a finalizer on this class. + private readonly SafeLibraryHandle libraryHandle; + } +} diff --git a/SimConnect/Properties/AssemblyInfo.cs b/SimConnect/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..281e444 --- /dev/null +++ b/SimConnect/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("SimConnect")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SimConnect")] +[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("f160bb6a-7620-41e5-a99c-948c208875e4")] + +// 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/SimConnect/SimConnect.csproj b/SimConnect/SimConnect.csproj new file mode 100644 index 0000000..84cdd03 --- /dev/null +++ b/SimConnect/SimConnect.csproj @@ -0,0 +1,98 @@ + + + + + Debug + AnyCPU + {F160BB6A-7620-41E5-A99C-948C208875E4} + Library + Properties + SimConnect + SimConnect + v4.7.2 + 512 + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + true + true + + + true + bin\x64\Debug\ + x64 + 7.3 + MinimumRecommendedRules.ruleset + true + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + 7.3 + prompt + MinimumRecommendedRules.ruleset + true + true + + + true + bin\x86\Debug\ + x86 + 7.3 + MinimumRecommendedRules.ruleset + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + Always + + + Always + + + Always + + + + \ No newline at end of file diff --git a/SimConnect/Unmanaged/SimConnectDLLLibrary.cs b/SimConnect/Unmanaged/SimConnectDLLLibrary.cs new file mode 100644 index 0000000..0bea960 --- /dev/null +++ b/SimConnect/Unmanaged/SimConnectDLLLibrary.cs @@ -0,0 +1,77 @@ +using System; +using SimConnect.Lib; + +#pragma warning disable 1591 +namespace SimConnect.Unmanaged +{ + public class SimConnectDLLLibrary : ISimConnectLibrary, IDisposable + { + private delegate uint SimConnectOpenProc(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex); + private delegate uint SimConnectCloseProc(IntPtr hSimConnect); + private delegate uint SimConnectAddToDataDefinitionProc(IntPtr hSimConnect, uint defineID, string datumName, string unitsName, SimConnectDataType datumType = SimConnectDataType.Float64, float epsilon = 0, uint datumID = uint.MaxValue); + private delegate uint SimConnectClearDataDefinitionProc(IntPtr hSimConnect, uint defineID); + private delegate uint SimConnectRequestDataOnSimObjectProc(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0); + private delegate uint SimConnectGetNextDispatchProc(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData); + + private readonly UnmanagedLibrary library; + + private readonly SimConnectOpenProc simConnectOpen; + private readonly SimConnectCloseProc simConnectClose; + private readonly SimConnectAddToDataDefinitionProc simConnectAddToDataDefinition; + private readonly SimConnectClearDataDefinitionProc simConnectClearDataDefinition; + private readonly SimConnectRequestDataOnSimObjectProc simConnectRequestDataOnSimObject; + private readonly SimConnectGetNextDispatchProc simConnectGetNextDispatch; + + + + public SimConnectDLLLibrary(string libraryFilename) + { + library = new UnmanagedLibrary(libraryFilename); + simConnectOpen = library.GetUnmanagedFunction("SimConnect_Open"); + simConnectClose = library.GetUnmanagedFunction("SimConnect_Close"); + simConnectAddToDataDefinition = library.GetUnmanagedFunction("SimConnect_AddToDataDefinition"); + simConnectClearDataDefinition = library.GetUnmanagedFunction("SimConnect_ClearDataDefinition"); + simConnectRequestDataOnSimObject = library.GetUnmanagedFunction("SimConnect_RequestDataOnSimObject"); + simConnectGetNextDispatch = library.GetUnmanagedFunction("SimConnect_GetNextDispatch"); + } + + public void Dispose() + { + library?.Dispose(); + } + + + public uint SimConnect_Open(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex) + { + return simConnectOpen(out phSimConnect, szName, hwnd, userEventWin32, hEventHandle, configIndex); + } + + public uint SimConnect_Close(IntPtr hSimConnect) + { + return simConnectClose(hSimConnect); + } + + + public uint SimConnect_AddToDataDefinition(IntPtr hSimConnect, uint defineID, string datumName, string unitsName, SimConnectDataType datumType = SimConnectDataType.Float64, float epsilon = 0, uint datumID = uint.MaxValue) + { + return simConnectAddToDataDefinition(hSimConnect, defineID, datumName, unitsName, datumType, epsilon, datumID); + } + + public uint SimConnect_ClearDataDefinition(IntPtr hSimConnect, uint defineID) + { + return simConnectClearDataDefinition(hSimConnect, defineID); + } + + + public uint SimConnect_RequestDataOnSimObject(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0) + { + return simConnectRequestDataOnSimObject(hSimConnect, requestID, defineID, objectID, period, flags, origin, interval, limit); + } + + public uint SimConnect_GetNextDispatch(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData) + { + return simConnectGetNextDispatch(hSimConnect, out ppData, out pcbData); + } + } +} +#pragma warning restore 1591 diff --git a/SimConnectUnitTests/Properties/AssemblyInfo.cs b/SimConnectUnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2063cd8 --- /dev/null +++ b/SimConnectUnitTests/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("SimConnectUnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SimConnectUnitTests")] +[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("2cd97d07-ef26-4cda-a090-df3d698e7f60")] + +// 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/SimConnectUnitTests/SimConnectDefinitionTest.cs b/SimConnectUnitTests/SimConnectDefinitionTest.cs new file mode 100644 index 0000000..73e298a --- /dev/null +++ b/SimConnectUnitTests/SimConnectDefinitionTest.cs @@ -0,0 +1,58 @@ +using System; +using FluentAssertions; +using SimConnect.Attribute; +using SimConnect.Concrete; +using Xunit; + +namespace SimConnectUnitTests +{ + public class SimConnectDefinitionTest + { + [Fact] + public void EmptyObject() + { + Action test = () => + { + var unused = new SimConnectDefinition(typeof(object)); + }; + + test.Should().Throw("the object is empty"); + } + + + // ReSharper disable UnusedMember.Global + // ReSharper disable UnusedMember.Local + #pragma warning disable 649 + private class NativeVariablesTest + { + [SimConnectVariable("INT")] + public int IntField; + + [SimConnectVariable("UINT")] + public uint UIntField; + + [SimConnectVariable("LONG")] + public long LongField; + + [SimConnectVariable("ULONG")] + public ulong ULongField; + + [SimConnectVariable("SINGLE")] + public float SingleProperty { get; set; } + + [SimConnectVariable("DOUBLE")] + public double DoubleProperty { get; set; } + } + #pragma warning restore 649 + // ReSharper restore UnusedMember.Local + // ReSharper restore UnusedMember.Global + + + [Fact] + public void NativeVariables() + { + var definition = new SimConnectDefinition(typeof(NativeVariablesTest)); + definition.Variables.Should().HaveCount(6); + } + } +} diff --git a/SimConnectUnitTests/SimConnectUnitTests.csproj b/SimConnectUnitTests/SimConnectUnitTests.csproj new file mode 100644 index 0000000..9aa0dc5 --- /dev/null +++ b/SimConnectUnitTests/SimConnectUnitTests.csproj @@ -0,0 +1,112 @@ + + + + + + Debug + AnyCPU + {2CD97D07-EF26-4CDA-A090-DF3D698E7F60} + Library + Properties + SimConnectUnitTests + SimConnectUnitTests + v4.7.2 + 512 + true + + + + + true + bin\x64\Debug %2832 bits%29\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x64\Release %2832 bits%29\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x86\Debug %2832 bits%29\ + DEBUG;TRACE + full + x86 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x86\Release %2832 bits%29\ + TRACE + true + pdbonly + x86 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + + ..\packages\FluentAssertions.5.10.3\lib\net47\FluentAssertions.dll + + + + + + + + + + + + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll + + + + + + + + + + + + + + + {f160bb6a-7620-41e5-a99c-948c208875e4} + SimConnect + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/SimConnectUnitTests/packages.config b/SimConnectUnitTests/packages.config new file mode 100644 index 0000000..31768df --- /dev/null +++ b/SimConnectUnitTests/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file