Full Code of astenlund/fs2ff for AI

master cad104f7be06 cached
46 files
146.0 KB
33.8k tokens
261 symbols
1 requests
Download .txt
Repository: astenlund/fs2ff
Branch: master
Commit: cad104f7be06
Files: 46
Total size: 146.0 KB

Directory structure:
gitextract_pdi8_1c4/

├── .editorconfig
├── .github/
│   └── dependabot.yml
├── .gitignore
├── .vscode/
│   ├── launch.json
│   └── tasks.json
├── ActionCommand.cs
├── Annotations.cs
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── Behaviors/
│   ├── MoveFocusOnEnterBehavior.cs
│   └── UpdateSourceOnLostFocusBehavior.cs
├── Converters/
│   ├── BooleanToDoubleConverter.cs
│   ├── BooleanToVisibilityConverter.cs
│   ├── FormatStringConverter.cs
│   ├── InvertedBooleanConverter.cs
│   ├── IpAddressToStringConverter.cs
│   └── UIntToDoubleConverter.cs
├── DataSender.cs
├── Extensions.cs
├── FodyWeavers.xml
├── FodyWeavers.xsd
├── IpDetectionService.cs
├── LICENSE
├── MainViewModel.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Models/
│   ├── Attitude.cs
│   ├── Position.cs
│   └── Traffic.cs
├── Preferences.Designer.cs
├── Preferences.settings
├── README.md
├── Resources.xaml
├── SimConnect/
│   ├── DEFINITION.cs
│   ├── EVENT.cs
│   ├── ISimConnectMessageHandler.cs
│   ├── REQUEST.cs
│   └── SimConnectAdapter.cs
├── UpdateChecker.cs
├── UpdateInformation.cs
├── ViewModelLocator.cs
├── fs2ff.csproj
├── fs2ff.sln
├── global.json
└── publish.ps1

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true

# ReSharper annotations
[Annotations.cs]
dotnet_diagnostic.CS8618.severity = none

# All files
[*.*]

max_line_length=160

# C# files
[*.cs]

#### Core EditorConfig Options ####

# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4

# New line preferences
end_of_line = crlf
insert_final_newline = true

#### .NET Coding Conventions ####

# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset

# this. and Me. preferences
dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent

# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent

# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent

# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent

# Expression-level preferences
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion

# Field preferences
dotnet_style_readonly_field = true:suggestion

# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion

#### C# Coding Conventions ####

# var preferences
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent

# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent

# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion

# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion

# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent

# Code-block preferences
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion

# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent

# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent

#### C# Formatting Rules ####

# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true

# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true

# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true

#### Naming styles ####

# Naming rules

# Symbol specifications

# Naming styles


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: "nuget"
    directory: "/"
    schedule:
      interval: "daily"
    rebase-strategy: "auto"
    reviewers:
      - "astenlund"
    assignees:
      - "astenlund"


================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore

# Image files
*.png

# Libraries
lib/*

# Published binaries
publish/*

# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Mono auto generated files
mono_crash.*

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# Visual Studio 2017 auto generated files
Generated\ Files/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# Benchmark Results
BenchmarkDotNet.Artifacts/

# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/

# StyleCop
StyleCopReport.xml

# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# Visual Studio Trace Files
*.e2e

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json

# Visual Studio code coverage results
*.coverage
*.coveragexml

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs

# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak

# SQL Server files
*.mdf
*.ldf
*.ndf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# CodeRush personal settings
.cr/personal

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config

# Tabs Studio
*.tss

# Telerik's JustMock configuration file
*.jmconfig

# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs

# OpenCover UI analysis results
OpenCover/

# Azure Stream Analytics local run output
ASALocalRun/

# MSBuild Binary and Structured Log
*.binlog

# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder
.mfractor/

# Local History for Visual Studio
.localhistory/

# BeatPulse healthcheck temp database
healthchecksdb

# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/

##
## Visual studio for Mac
##


# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/

# Mac bundle stuff
*.dmg
*.app

# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

# JetBrains Rider
.idea/
*.sln.iml

##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json


================================================
FILE: .vscode/launch.json
================================================
{
  "version": "0.2.0",
  "configurations": [
    {
      // Use IntelliSense to find out which attributes exist for C# debugging
      // Use hover for the description of the existing attributes
      // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
      "name": ".NET Core Launch (console)",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "build",
      // If you have changed target frameworks, make sure to update the program path.
      "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/win-x64/fs2ff.exe",
      "args": [],
      "cwd": "${workspaceFolder}",
      // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
      "console": "internalConsole",
      "stopAtEntry": false
    },
    {
      "name": ".NET Core Attach",
      "type": "coreclr",
      "request": "attach"
    }
  ]
}

================================================
FILE: .vscode/tasks.json
================================================
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build",
      "command": "dotnet",
      "type": "process",
      "args": [
        "build",
        "${workspaceFolder}/fs2ff.csproj",
        "/property:GenerateFullPaths=true",
        "/consoleloggerparameters:NoSummary"
      ],
      "problemMatcher": "$msCompile"
    },
    {
      "label": "publish",
      "command": "dotnet",
      "type": "process",
      "args": [
        "publish",
        "${workspaceFolder}/fs2ff.csproj",
        "/property:GenerateFullPaths=true",
        "/consoleloggerparameters:NoSummary"
      ],
      "problemMatcher": "$msCompile"
    },
    {
      "label": "watch",
      "command": "dotnet",
      "type": "process",
      "args": [
        "watch",
        "run",
        "${workspaceFolder}/fs2ff.csproj",
        "/property:GenerateFullPaths=true",
        "/consoleloggerparameters:NoSummary"
      ],
      "problemMatcher": "$msCompile"
    }
  ]
}

================================================
FILE: ActionCommand.cs
================================================
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global

using System;
using System.Windows.Input;

namespace fs2ff
{
    public class ActionCommand : ICommand
    {
        private readonly Action _action;
        private readonly Func<bool> _predicate;

        public ActionCommand()
        {
            _action = () => { };
            _predicate = () => true;
        }

        public ActionCommand(Action action)
        {
            _action = action;
            _predicate = () => true;
        }

        public ActionCommand(Action action, Func<bool> predicate)
        {
            _action = action;
            _predicate = predicate;
        }

        public event EventHandler? CanExecuteChanged;

        public bool CanExecute(object? _) => _predicate();

        public void Execute(object? parameter) => _action();

        public void TriggerCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs());
    }

    public class ActionCommand<T> : ICommand where T : struct
    {
        private readonly Action<T?> _action;
        private readonly Func<T?, bool> _predicate;

        public ActionCommand()
        {
            _action = _ => { };
            _predicate = _ => true;
        }

        public ActionCommand(Action<T?> action)
        {
            _action = action;
            _predicate = _ => true;
        }

        public ActionCommand(Action<T?> action, Func<T?, bool> predicate)
        {
            _action = action;
            _predicate = predicate;
        }

        public event EventHandler? CanExecuteChanged;

        public bool CanExecute(object? parameter) => parameter is T param ? _predicate(param) : _predicate(null);

        public void Execute(object? parameter)
        {
            if (parameter is T param) _action(param);
            else                      _action(null);
        }

        public void TriggerCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs());
    }
}


================================================
FILE: Annotations.cs
================================================
/* MIT License

Copyright (c) 2016 JetBrains http://www.jetbrains.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. */

using System;
// ReSharper disable InheritdocConsiderUsage

#pragma warning disable 1591
// ReSharper disable UnusedMember.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable IntroduceOptionalParameters.Global
// ReSharper disable MemberCanBeProtected.Global
// ReSharper disable InconsistentNaming

namespace fs2ff.Annotations
{
  /// <summary>
  /// Indicates that the value of the marked element could be <c>null</c> sometimes,
  /// so checking for <c>null</c> is required before its usage.
  /// </summary>
  /// <example><code>
  /// [CanBeNull] object Test() => null;
  /// 
  /// void UseTest() {
  ///   var p = Test();
  ///   var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
    AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
    AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
  public sealed class CanBeNullAttribute : Attribute { }

  /// <summary>
  /// Indicates that the value of the marked element can never be <c>null</c>.
  /// </summary>
  /// <example><code>
  /// [NotNull] object Foo() {
  ///   return null; // Warning: Possible 'null' assignment
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
    AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
    AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
  public sealed class NotNullAttribute : Attribute { }

  /// <summary>
  /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task
  /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property
  /// or of the Lazy.Value property can never be null.
  /// </summary>
  /// <example><code>
  /// public void Foo([ItemNotNull]List&lt;string&gt; books)
  /// {
  ///   foreach (var book in books) {
  ///     if (book != null) // Warning: Expression is always true
  ///      Console.WriteLine(book.ToUpper());
  ///   }
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
    AttributeTargets.Delegate | AttributeTargets.Field)]
  public sealed class ItemNotNullAttribute : Attribute { }

  /// <summary>
  /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task
  /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property
  /// or of the Lazy.Value property can be null.
  /// </summary>
  /// <example><code>
  /// public void Foo([ItemCanBeNull]List&lt;string&gt; books)
  /// {
  ///   foreach (var book in books)
  ///   {
  ///     // Warning: Possible 'System.NullReferenceException'
  ///     Console.WriteLine(book.ToUpper());
  ///   }
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
    AttributeTargets.Delegate | AttributeTargets.Field)]
  public sealed class ItemCanBeNullAttribute : Attribute { }

  /// <summary>
  /// Indicates that the marked method builds string by the format pattern and (optional) arguments.
  /// The parameter, which contains the format string, should be given in constructor. The format string
  /// should be in <see cref="string.Format(IFormatProvider,string,object[])"/>-like form.
  /// </summary>
  /// <example><code>
  /// [StringFormatMethod("message")]
  /// void ShowError(string message, params object[] args) { /* do something */ }
  /// 
  /// void Foo() {
  ///   ShowError("Failed: {0}"); // Warning: Non-existing argument in format string
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Constructor | AttributeTargets.Method |
    AttributeTargets.Property | AttributeTargets.Delegate)]
  public sealed class StringFormatMethodAttribute : Attribute
  {
    /// <param name="formatParameterName">
    /// Specifies which parameter of an annotated method should be treated as the format string
    /// </param>
    public StringFormatMethodAttribute([NotNull] string formatParameterName)
    {
      FormatParameterName = formatParameterName;
    }

    [NotNull] public string FormatParameterName { get; }
  }

  /// <summary>
  /// Use this annotation to specify a type that contains static or const fields
  /// with values for the annotated property/field/parameter.
  /// The specified type will be used to improve completion suggestions.
  /// </summary>
  /// <example><code>
  /// namespace TestNamespace
  /// {
  ///   public class Constants
  ///   {
  ///     public static int INT_CONST = 1;
  ///     public const string STRING_CONST = "1";
  ///   }
  ///
  ///   public class Class1
  ///   {
  ///     [ValueProvider("TestNamespace.Constants")] public int myField;
  ///     public void Foo([ValueProvider("TestNamespace.Constants")] string str) { }
  ///
  ///     public void Test()
  ///     {
  ///       Foo(/*try completion here*/);//
  ///       myField = /*try completion here*/
  ///     }
  ///   }
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field,
    AllowMultiple = true)]
  public sealed class ValueProviderAttribute : Attribute
  {
    public ValueProviderAttribute([NotNull] string name)
    {
      Name = name;
    }

    [NotNull] public string Name { get; }
  }

  /// <summary>
  /// Indicates that the integral value falls into the specified interval.
  /// It's allowed to specify multiple non-intersecting intervals.
  /// Values of interval boundaries are inclusive.
  /// </summary>
  /// <example><code>
  /// void Foo([ValueRange(0, 100)] int value) {
  ///   if (value == -1) { // Warning: Expression is always 'false'
  ///     ...
  ///   }
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property |
    AttributeTargets.Method | AttributeTargets.Delegate,
    AllowMultiple = true)]
  public sealed class ValueRangeAttribute : Attribute
  {
    public object From { get; }
    public object To { get; }

    public ValueRangeAttribute(long from, long to)
    {
      From = from;
      To = to;
    }

    public ValueRangeAttribute(ulong from, ulong to)
    {
      From = from;
      To = to;
    }

    public ValueRangeAttribute(long value)
    {
      From = To = value;
    }

    public ValueRangeAttribute(ulong value)
    {
      From = To = value;
    }
  }

  /// <summary>
  /// Indicates that the integral value never falls below zero.
  /// </summary>
  /// <example><code>
  /// void Foo([NonNegativeValue] int value) {
  ///   if (value == -1) { // Warning: Expression is always 'false'
  ///     ...
  ///   }
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property |
    AttributeTargets.Method | AttributeTargets.Delegate)]
  public sealed class NonNegativeValueAttribute : Attribute { }

  /// <summary>
  /// Indicates that the function argument should be a string literal and match one
  /// of the parameters of the caller function. For example, ReSharper annotates
  /// the parameter of <see cref="System.ArgumentNullException"/>.
  /// </summary>
  /// <example><code>
  /// void Foo(string param) {
  ///   if (param == null)
  ///     throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol
  /// }
  /// </code></example>
  [AttributeUsage(AttributeTargets.Parameter)]
  public sealed class InvokerParameterNameAttribute : Attribute { }

  /// <summary>
  /// Indicates that the method is contained in a type that implements
  /// <c>System.ComponentModel.INotifyPropertyChanged</c> interface and this method
  /// is used to notify that some property value changed.
  /// </summary>
  /// <remarks>
  /// The method should be non-static and conform to one of the supported signatures:
  /// <list>
  /// <item><c>NotifyChanged(string)</c></item>
  /// <item><c>NotifyChanged(params string[])</c></item>
  /// <item><c>NotifyChanged{T}(Expression{Func{T}})</c></item>
  /// <item><c>NotifyChanged{T,U}(Expression{Func{T,U}})</c></item>
  /// <item><c>SetProperty{T}(ref T, T, string)</c></item>
  /// </list>
  /// </remarks>
  /// <example><code>
  /// public class Foo : INotifyPropertyChanged {
  ///   public event PropertyChangedEventHandler PropertyChanged;
  /// 
  ///   [NotifyPropertyChangedInvocator]
  ///   protected virtual void NotifyChanged(string propertyName) { ... }
  ///
  ///   string _name;
  /// 
  ///   public string Name {
  ///     get { return _name; }
  ///     set { _name = value; NotifyChanged("LastName"); /* Warning */ }
  ///   }
  /// }
  /// </code>
  /// Examples of generated notifications:
  /// <list>
  /// <item><c>NotifyChanged("Property")</c></item>
  /// <item><c>NotifyChanged(() =&gt; Property)</c></item>
  /// <item><c>NotifyChanged((VM x) =&gt; x.Property)</c></item>
  /// <item><c>SetProperty(ref myField, value, "Property")</c></item>
  /// </list>
  /// </example>
  [AttributeUsage(AttributeTargets.Method)]
  public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute
  {
    public NotifyPropertyChangedInvocatorAttribute() { }
    public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)
    {
      ParameterName = parameterName;
    }

    [CanBeNull] public string ParameterName { get; }
  }

  /// <summary>
  /// Describes dependency between method input and output.
  /// </summary>
  /// <syntax>
  /// <p>Function Definition Table syntax:</p>
  /// <list>
  /// <item>FDT      ::= FDTRow [;FDTRow]*</item>
  /// <item>FDTRow   ::= Input =&gt; Output | Output &lt;= Input</item>
  /// <item>Input    ::= ParameterName: Value [, Input]*</item>
  /// <item>Output   ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}</item>
  /// <item>Value    ::= true | false | null | notnull | canbenull</item>
  /// </list>
  /// If the method has a single input parameter, its name could be omitted.<br/>
  /// Using <c>halt</c> (or <c>void</c>/<c>nothing</c>, which is the same) for the method output
  /// means that the method doesn't return normally (throws or terminates the process).<br/>
  /// Value <c>canbenull</c> is only applicable for output parameters.<br/>
  /// You can use multiple <c>[ContractAnnotation]</c> for each FDT row, or use single attribute
  /// with rows separated by semicolon. There is no notion of order rows, all rows are checked
  /// for applicability and applied per each program state tracked by the analysis engine.<br/>
  /// </syntax>
  /// <examples><list>
  /// <item><code>
  /// [ContractAnnotation("=&gt; halt")]
  /// public void TerminationMethod()
  /// </code></item>
  /// <item><code>
  /// [ContractAnnotation("null &lt;= param:null")] // reverse condition syntax
  /// public string GetName(string surname)
  /// </code></item>
  /// <item><code>
  /// [ContractAnnotation("s:null =&gt; true")]
  /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty()
  /// </code></item>
  /// <item><code>
  /// // A method that returns null if the parameter is null,
  /// // and not null if the parameter is not null
  /// [ContractAnnotation("null =&gt; null; notnull =&gt; notnull")]
  /// public object Transform(object data)
  /// </code></item>
  /// <item><code>
  /// [ContractAnnotation("=&gt; true, result: notnull; =&gt; false, result: null")]
  /// public bool TryParse(string s, out Person result)
  /// </code></item>
  /// </list></examples>
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
  public sealed class ContractAnnotationAttribute : Attribute
  {
    public ContractAnnotationAttribute([NotNull] string contract)
      : this(contract, false) { }

    public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates)
    {
      Contract = contract;
      ForceFullStates = forceFullStates;
    }

    [NotNull] public string Contract { get; }

    public bool ForceFullStates { get; }
  }

  /// <summary>
  /// Indicates whether the marked element should be localized.
  /// </summary>
  /// <example><code>
  /// [LocalizationRequiredAttribute(true)]
  /// class Foo {
  ///   string str = "my string"; // Warning: Localizable string
  /// }
  /// </code></example>
  [AttributeUsage(AttributeTargets.All)]
  public sealed class LocalizationRequiredAttribute : Attribute
  {
    public LocalizationRequiredAttribute() : this(true) { }

    public LocalizationRequiredAttribute(bool required)
    {
      Required = required;
    }

    public bool Required { get; }
  }

  /// <summary>
  /// Indicates that the value of the marked type (or its derivatives)
  /// cannot be compared using '==' or '!=' operators and <c>Equals()</c>
  /// should be used instead. However, using '==' or '!=' for comparison
  /// with <c>null</c> is always permitted.
  /// </summary>
  /// <example><code>
  /// [CannotApplyEqualityOperator]
  /// class NoEquality { }
  /// 
  /// class UsesNoEquality {
  ///   void Test() {
  ///     var ca1 = new NoEquality();
  ///     var ca2 = new NoEquality();
  ///     if (ca1 != null) { // OK
  ///       bool condition = ca1 == ca2; // Warning
  ///     }
  ///   }
  /// }
  /// </code></example>
  [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)]
  public sealed class CannotApplyEqualityOperatorAttribute : Attribute { }

  /// <summary>
  /// When applied to a target attribute, specifies a requirement for any type marked
  /// with the target attribute to implement or inherit specific type or types.
  /// </summary>
  /// <example><code>
  /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement
  /// class ComponentAttribute : Attribute { }
  /// 
  /// [Component] // ComponentAttribute requires implementing IComponent interface
  /// class MyComponent : IComponent { }
  /// </code></example>
  [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
  [BaseTypeRequired(typeof(Attribute))]
  public sealed class BaseTypeRequiredAttribute : Attribute
  {
    public BaseTypeRequiredAttribute([NotNull] Type baseType)
    {
      BaseType = baseType;
    }

    [NotNull] public Type BaseType { get; }
  }

  /// <summary>
  /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library),
  /// so this symbol will not be reported as unused (as well as by other usage inspections).
  /// </summary>
  [AttributeUsage(AttributeTargets.All)]
  public sealed class UsedImplicitlyAttribute : Attribute
  {
    public UsedImplicitlyAttribute()
      : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }

    public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)
      : this(useKindFlags, ImplicitUseTargetFlags.Default) { }

    public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)
      : this(ImplicitUseKindFlags.Default, targetFlags) { }

    public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
    {
      UseKindFlags = useKindFlags;
      TargetFlags = targetFlags;
    }

    public ImplicitUseKindFlags UseKindFlags { get; }

    public ImplicitUseTargetFlags TargetFlags { get; }
  }

  /// <summary>
  /// Can be applied to attributes, type parameters, and parameters of a type assignable from <see cref="System.Type"/> .
  /// When applied to an attribute, the decorated attribute behaves the same as <see cref="UsedImplicitlyAttribute"/>.
  /// When applied to a type parameter or to a parameter of type <see cref="System.Type"/>,  indicates that the corresponding type
  /// is used implicitly.
  /// </summary>
  [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter | AttributeTargets.Parameter)]
  public sealed class MeansImplicitUseAttribute : Attribute
  {
    public MeansImplicitUseAttribute()
      : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }

    public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags)
      : this(useKindFlags, ImplicitUseTargetFlags.Default) { }

    public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags)
      : this(ImplicitUseKindFlags.Default, targetFlags) { }

    public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
    {
      UseKindFlags = useKindFlags;
      TargetFlags = targetFlags;
    }

    [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; }

    [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; }
  }

  /// <summary>
  /// Specify the details of implicitly used symbol when it is marked
  /// with <see cref="MeansImplicitUseAttribute"/> or <see cref="UsedImplicitlyAttribute"/>.
  /// </summary>
  [Flags]
  public enum ImplicitUseKindFlags
  {
    Default = Access | Assign | InstantiatedWithFixedConstructorSignature,
    /// <summary>Only entity marked with attribute considered used.</summary>
    Access = 1,
    /// <summary>Indicates implicit assignment to a member.</summary>
    Assign = 2,
    /// <summary>
    /// Indicates implicit instantiation of a type with fixed constructor signature.
    /// That means any unused constructor parameters won't be reported as such.
    /// </summary>
    InstantiatedWithFixedConstructorSignature = 4,
    /// <summary>Indicates implicit instantiation of a type.</summary>
    InstantiatedNoFixedConstructorSignature = 8,
  }

  /// <summary>
  /// Specify what is considered to be used implicitly when marked
  /// with <see cref="MeansImplicitUseAttribute"/> or <see cref="UsedImplicitlyAttribute"/>.
  /// </summary>
  [Flags]
  public enum ImplicitUseTargetFlags
  {
    Default = Itself,
    Itself = 1,
    /// <summary>Members of entity marked with attribute are considered used.</summary>
    Members = 2,
    /// <summary> Inherited entities are considered used. </summary>
    WithInheritors = 4,
    /// <summary>Entity marked with attribute and all its members considered used.</summary>
    WithMembers = Itself | Members
  }

  /// <summary>
  /// This attribute is intended to mark publicly available API
  /// which should not be removed and so is treated as used.
  /// </summary>
  [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
  [AttributeUsage(AttributeTargets.All, Inherited = false)]
  public sealed class PublicAPIAttribute : Attribute
  {
    public PublicAPIAttribute() { }

    public PublicAPIAttribute([NotNull] string comment)
    {
      Comment = comment;
    }

    [CanBeNull] public string Comment { get; }
  }

  /// <summary>
  /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack.
  /// If the parameter is a delegate, indicates that delegate is executed while the method is executed.
  /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter)]
  public sealed class InstantHandleAttribute : Attribute { }

  /// <summary>
  /// Indicates that a method does not make any observable state changes.
  /// The same as <c>System.Diagnostics.Contracts.PureAttribute</c>.
  /// </summary>
  /// <example><code>
  /// [Pure] int Multiply(int x, int y) => x * y;
  /// 
  /// void M() {
  ///   Multiply(123, 42); // Warning: Return value of pure method is not used
  /// }
  /// </code></example>
  [AttributeUsage(AttributeTargets.Method)]
  public sealed class PureAttribute : Attribute { }

  /// <summary>
  /// Indicates that the return value of the method invocation must be used.
  /// </summary>
  /// <remarks>
  /// Methods decorated with this attribute (in contrast to pure methods) might change state,
  /// but make no sense without using their return value. <br/>
  /// Similarly to <see cref="PureAttribute"/>, this attribute
  /// will help detecting usages of the method when the return value in not used.
  /// Additionally, you can optionally specify a custom message, which will be used when showing warnings, e.g.
  /// <code>[MustUseReturnValue("Use the return value to...")]</code>.
  /// </remarks>
  [AttributeUsage(AttributeTargets.Method)]
  public sealed class MustUseReturnValueAttribute : Attribute
  {
    public MustUseReturnValueAttribute() { }

    public MustUseReturnValueAttribute([NotNull] string justification)
    {
      Justification = justification;
    }

    [CanBeNull] public string Justification { get; }
  }

  /// <summary>
  /// Indicates the type member or parameter of some type, that should be used instead of all other ways
  /// to get the value of that type. This annotation is useful when you have some "context" value evaluated
  /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one.
  /// </summary>
  /// <example><code>
  /// class Foo {
  ///   [ProvidesContext] IBarService _barService = ...;
  /// 
  ///   void ProcessNode(INode node) {
  ///     DoSomething(node, node.GetGlobalServices().Bar);
  ///     //              ^ Warning: use value of '_barService' field
  ///   }
  /// }
  /// </code></example>
  [AttributeUsage(
    AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method |
    AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)]
  public sealed class ProvidesContextAttribute : Attribute { }

  /// <summary>
  /// Indicates that a parameter is a path to a file or a folder within a web project.
  /// Path can be relative or absolute, starting from web root (~).
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter)]
  public sealed class PathReferenceAttribute : Attribute
  {
    public PathReferenceAttribute() { }

    public PathReferenceAttribute([NotNull, PathReference] string basePath)
    {
      BasePath = basePath;
    }

    [CanBeNull] public string BasePath { get; }
  }

  /// <summary>
  /// An extension method marked with this attribute is processed by code completion
  /// as a 'Source Template'. When the extension method is completed over some expression, its source code
  /// is automatically expanded like a template at call site.
  /// </summary>
  /// <remarks>
  /// Template method body can contain valid source code and/or special comments starting with '$'.
  /// Text inside these comments is added as source code when the template is applied. Template parameters
  /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs.
  /// Use the <see cref="MacroAttribute"/> attribute to specify macros for parameters.
  /// </remarks>
  /// <example>
  /// In this example, the 'forEach' method is a source template available over all values
  /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block:
  /// <code>
  /// [SourceTemplate]
  /// public static void forEach&lt;T&gt;(this IEnumerable&lt;T&gt; xs) {
  ///   foreach (var x in xs) {
  ///      //$ $END$
  ///   }
  /// }
  /// </code>
  /// </example>
  [AttributeUsage(AttributeTargets.Method)]
  public sealed class SourceTemplateAttribute : Attribute { }

  /// <summary>
  /// Allows specifying a macro for a parameter of a <see cref="SourceTemplateAttribute">source template</see>.
  /// </summary>
  /// <remarks>
  /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression
  /// is defined in the <see cref="MacroAttribute.Expression"/> property. When applied on a method, the target
  /// template parameter is defined in the <see cref="MacroAttribute.Target"/> property. To apply the macro silently
  /// for the parameter, set the <see cref="MacroAttribute.Editable"/> property value = -1.
  /// </remarks>
  /// <example>
  /// Applying the attribute on a source template method:
  /// <code>
  /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")]
  /// public static void forEach&lt;T&gt;(this IEnumerable&lt;T&gt; collection) {
  ///   foreach (var item in collection) {
  ///     //$ $END$
  ///   }
  /// }
  /// </code>
  /// Applying the attribute on a template method parameter:
  /// <code>
  /// [SourceTemplate]
  /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) {
  ///   /*$ var $x$Id = "$newguid$" + x.ToString();
  ///   x.DoSomething($x$Id); */
  /// }
  /// </code>
  /// </example>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)]
  public sealed class MacroAttribute : Attribute
  {
    /// <summary>
    /// Allows specifying a macro that will be executed for a <see cref="SourceTemplateAttribute">source template</see>
    /// parameter when the template is expanded.
    /// </summary>
    [CanBeNull] public string Expression { get; set; }

    /// <summary>
    /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed.
    /// </summary>
    /// <remarks>
    /// If the target parameter is used several times in the template, only one occurrence becomes editable;
    /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence,
    /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1.
    /// </remarks>
    public int Editable { get; set; }

    /// <summary>
    /// Identifies the target parameter of a <see cref="SourceTemplateAttribute">source template</see> if the
    /// <see cref="MacroAttribute"/> is applied on a template method.
    /// </summary>
    [CanBeNull] public string Target { get; set; }
  }

  [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
  public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute
  {
    public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format)
    {
      Format = format;
    }

    [NotNull] public string Format { get; }
  }

  [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
  public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute
  {
    public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format)
    {
      Format = format;
    }

    [NotNull] public string Format { get; }
  }

  [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
  public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute
  {
    public AspMvcAreaViewLocationFormatAttribute([NotNull] string format)
    {
      Format = format;
    }

    [NotNull] public string Format { get; }
  }

  [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
  public sealed class AspMvcMasterLocationFormatAttribute : Attribute
  {
    public AspMvcMasterLocationFormatAttribute([NotNull] string format)
    {
      Format = format;
    }

    [NotNull] public string Format { get; }
  }

  [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
  public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute
  {
    public AspMvcPartialViewLocationFormatAttribute([NotNull] string format)
    {
      Format = format;
    }

    [NotNull] public string Format { get; }
  }

  [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
  public sealed class AspMvcViewLocationFormatAttribute : Attribute
  {
    public AspMvcViewLocationFormatAttribute([NotNull] string format)
    {
      Format = format;
    }

    [NotNull] public string Format { get; }
  }

  /// <summary>
  /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
  /// is an MVC action. If applied to a method, the MVC action name is calculated
  /// implicitly from the context. Use this attribute for custom wrappers similar to
  /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcActionAttribute : Attribute
  {
    public AspMvcActionAttribute() { }

    public AspMvcActionAttribute([NotNull] string anonymousProperty)
    {
      AnonymousProperty = anonymousProperty;
    }

    [CanBeNull] public string AnonymousProperty { get; }
  }

  /// <summary>
  /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC area.
  /// Use this attribute for custom wrappers similar to
  /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcAreaAttribute : Attribute
  {
    public AspMvcAreaAttribute() { }

    public AspMvcAreaAttribute([NotNull] string anonymousProperty)
    {
      AnonymousProperty = anonymousProperty;
    }

    [CanBeNull] public string AnonymousProperty { get; }
  }

  /// <summary>
  /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is
  /// an MVC controller. If applied to a method, the MVC controller name is calculated
  /// implicitly from the context. Use this attribute for custom wrappers similar to
  /// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcControllerAttribute : Attribute
  {
    public AspMvcControllerAttribute() { }

    public AspMvcControllerAttribute([NotNull] string anonymousProperty)
    {
      AnonymousProperty = anonymousProperty;
    }

    [CanBeNull] public string AnonymousProperty { get; }
  }

  /// <summary>
  /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC Master. Use this attribute
  /// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcMasterAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC model type. Use this attribute
  /// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, Object)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter)]
  public sealed class AspMvcModelTypeAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC
  /// partial view. If applied to a method, the MVC partial view name is calculated implicitly
  /// from the context. Use this attribute for custom wrappers similar to
  /// <c>System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcPartialViewAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method.
  /// </summary>
  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  public sealed class AspMvcSuppressViewErrorAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template.
  /// Use this attribute for custom wrappers similar to
  /// <c>System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcDisplayTemplateAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC editor template.
  /// Use this attribute for custom wrappers similar to
  /// <c>System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcEditorTemplateAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC template.
  /// Use this attribute for custom wrappers similar to
  /// <c>System.ComponentModel.DataAnnotations.UIHintAttribute(System.String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcTemplateAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
  /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly
  /// from the context. Use this attribute for custom wrappers similar to
  /// <c>System.Web.Mvc.Controller.View(Object)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcViewAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
  /// is an MVC view component name.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcViewComponentAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
  /// is an MVC view component view. If applied to a method, the MVC view component view name is default.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class AspMvcViewComponentViewAttribute : Attribute { }

  /// <summary>
  /// ASP.NET MVC attribute. When applied to a parameter of an attribute,
  /// indicates that this parameter is an MVC action name.
  /// </summary>
  /// <example><code>
  /// [ActionName("Foo")]
  /// public ActionResult Login(string returnUrl) {
  ///   ViewBag.ReturnUrl = Url.Action("Foo"); // OK
  ///   return RedirectToAction("Bar"); // Error: Cannot resolve action
  /// }
  /// </code></example>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
  public sealed class AspMvcActionSelectorAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]
  public sealed class HtmlElementAttributesAttribute : Attribute
  {
    public HtmlElementAttributesAttribute() { }

    public HtmlElementAttributesAttribute([NotNull] string name)
    {
      Name = name;
    }

    [CanBeNull] public string Name { get; }
  }

  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class HtmlAttributeValueAttribute : Attribute
  {
    public HtmlAttributeValueAttribute([NotNull] string name)
    {
      Name = name;
    }

    [NotNull] public string Name { get; }
  }

  /// <summary>
  /// Razor attribute. Indicates that the marked parameter or method is a Razor section.
  /// Use this attribute for custom wrappers similar to
  /// <c>System.Web.WebPages.WebPageBase.RenderSection(String)</c>.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
  public sealed class RazorSectionAttribute : Attribute { }

  /// <summary>
  /// Indicates how method, constructor invocation, or property access
  /// over collection type affects the contents of the collection.
  /// Use <see cref="CollectionAccessType"/> to specify the access type.
  /// </summary>
  /// <remarks>
  /// Using this attribute only makes sense if all collection methods are marked with this attribute.
  /// </remarks>
  /// <example><code>
  /// public class MyStringCollection : List&lt;string&gt;
  /// {
  ///   [CollectionAccess(CollectionAccessType.Read)]
  ///   public string GetFirstString()
  ///   {
  ///     return this.ElementAt(0);
  ///   }
  /// }
  /// class Test
  /// {
  ///   public void Foo()
  ///   {
  ///     // Warning: Contents of the collection is never updated
  ///     var col = new MyStringCollection();
  ///     string x = col.GetFirstString();
  ///   }
  /// }
  /// </code></example>
  [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)]
  public sealed class CollectionAccessAttribute : Attribute
  {
    public CollectionAccessAttribute(CollectionAccessType collectionAccessType)
    {
      CollectionAccessType = collectionAccessType;
    }

    public CollectionAccessType CollectionAccessType { get; }
  }

  /// <summary>
  /// Provides a value for the <see cref="CollectionAccessAttribute"/> to define
  /// how the collection method invocation affects the contents of the collection.
  /// </summary>
  [Flags]
  public enum CollectionAccessType
  {
    /// <summary>Method does not use or modify content of the collection.</summary>
    None = 0,
    /// <summary>Method only reads content of the collection but does not modify it.</summary>
    Read = 1,
    /// <summary>Method can change content of the collection but does not add new elements.</summary>
    ModifyExistingContent = 2,
    /// <summary>Method can add new elements to the collection.</summary>
    UpdatedContent = ModifyExistingContent | 4
  }

  /// <summary>
  /// Indicates that the marked method is assertion method, i.e. it halts the control flow if
  /// one of the conditions is satisfied. To set the condition, mark one of the parameters with
  /// <see cref="AssertionConditionAttribute"/> attribute.
  /// </summary>
  [AttributeUsage(AttributeTargets.Method)]
  public sealed class AssertionMethodAttribute : Attribute { }

  /// <summary>
  /// Indicates the condition parameter of the assertion method. The method itself should be
  /// marked by <see cref="AssertionMethodAttribute"/> attribute. The mandatory argument of
  /// the attribute is the assertion type.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter)]
  public sealed class AssertionConditionAttribute : Attribute
  {
    public AssertionConditionAttribute(AssertionConditionType conditionType)
    {
      ConditionType = conditionType;
    }

    public AssertionConditionType ConditionType { get; }
  }

  /// <summary>
  /// Specifies assertion type. If the assertion method argument satisfies the condition,
  /// then the execution continues. Otherwise, execution is assumed to be halted.
  /// </summary>
  public enum AssertionConditionType
  {
    /// <summary>Marked parameter should be evaluated to true.</summary>
    IS_TRUE = 0,
    /// <summary>Marked parameter should be evaluated to false.</summary>
    IS_FALSE = 1,
    /// <summary>Marked parameter should be evaluated to null value.</summary>
    IS_NULL = 2,
    /// <summary>Marked parameter should be evaluated to not null value.</summary>
    IS_NOT_NULL = 3,
  }

  /// <summary>
  /// Indicates that the marked method unconditionally terminates control flow execution.
  /// For example, it could unconditionally throw exception.
  /// </summary>
  [Obsolete("Use [ContractAnnotation('=> halt')] instead")]
  [AttributeUsage(AttributeTargets.Method)]
  public sealed class TerminatesProgramAttribute : Attribute { }

  /// <summary>
  /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select,
  /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters
  /// of delegate type by analyzing LINQ method chains.
  /// </summary>
  [AttributeUsage(AttributeTargets.Method)]
  public sealed class LinqTunnelAttribute : Attribute { }

  /// <summary>
  /// Indicates that IEnumerable passed as a parameter is not enumerated.
  /// Use this annotation to suppress the 'Possible multiple enumeration of IEnumerable' inspection.
  /// </summary>
  /// <example><code>
  /// static void ThrowIfNull&lt;T&gt;([NoEnumeration] T v, string n) where T : class
  /// {
  ///   // custom check for null but no enumeration
  /// }
  /// 
  /// void Foo(IEnumerable&lt;string&gt; values)
  /// {
  ///   ThrowIfNull(values, nameof(values));
  ///   var x = values.ToList(); // No warnings about multiple enumeration
  /// }
  /// </code></example>
  [AttributeUsage(AttributeTargets.Parameter)]
  public sealed class NoEnumerationAttribute : Attribute { }

  /// <summary>
  /// Indicates that the marked parameter is a regular expression pattern.
  /// </summary>
  [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
  public sealed class RegexPatternAttribute : Attribute { }

  /// <summary>
  /// Prevents the Member Reordering feature from tossing members of the marked class.
  /// </summary>
  /// <remarks>
  /// The attribute must be mentioned in your member reordering patterns.
  /// </remarks>
  [AttributeUsage(
    AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)]
  public sealed class NoReorderAttribute : Attribute { }

  /// <summary>
  /// XAML attribute. Indicates the type that has <c>ItemsSource</c> property and should be treated
  /// as <c>ItemsControl</c>-derived type, to enable inner items <c>DataContext</c> type resolve.
  /// </summary>
  [AttributeUsage(AttributeTargets.Class)]
  public sealed class XamlItemsControlAttribute : Attribute { }

  /// <summary>
  /// XAML attribute. Indicates the property of some <c>BindingBase</c>-derived type, that
  /// is used to bind some item of <c>ItemsControl</c>-derived type. This annotation will
  /// enable the <c>DataContext</c> type resolve for XAML bindings for such properties.
  /// </summary>
  /// <remarks>
  /// Property should have the tree ancestor of the <c>ItemsControl</c> type or
  /// marked with the <see cref="XamlItemsControlAttribute"/> attribute.
  /// </remarks>
  [AttributeUsage(AttributeTargets.Property)]
  public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { }

  /// <summary>
  /// XAML attribute. Indicates the property of some <c>Style</c>-derived type, that
  /// is used to style items of <c>ItemsControl</c>-derived type. This annotation will
  /// enable the <c>DataContext</c> type resolve for XAML bindings for such properties.
  /// </summary>
  /// <remarks>
  /// Property should have the tree ancestor of the <c>ItemsControl</c> type or
  /// marked with the <see cref="XamlItemsControlAttribute"/> attribute.
  /// </remarks>
  [AttributeUsage(AttributeTargets.Property)]
  public sealed class XamlItemStyleOfItemsControlAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
  public sealed class AspChildControlTypeAttribute : Attribute
  {
    public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType)
    {
      TagName = tagName;
      ControlType = controlType;
    }

    [NotNull] public string TagName { get; }

    [NotNull] public Type ControlType { get; }
  }

  [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
  public sealed class AspDataFieldAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
  public sealed class AspDataFieldsAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Property)]
  public sealed class AspMethodPropertyAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
  public sealed class AspRequiredAttributeAttribute : Attribute
  {
    public AspRequiredAttributeAttribute([NotNull] string attribute)
    {
      Attribute = attribute;
    }

    [NotNull] public string Attribute { get; }
  }

  [AttributeUsage(AttributeTargets.Property)]
  public sealed class AspTypePropertyAttribute : Attribute
  {
    public bool CreateConstructorReferences { get; }

    public AspTypePropertyAttribute(bool createConstructorReferences)
    {
      CreateConstructorReferences = createConstructorReferences;
    }
  }

  [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
  public sealed class RazorImportNamespaceAttribute : Attribute
  {
    public RazorImportNamespaceAttribute([NotNull] string name)
    {
      Name = name;
    }

    [NotNull] public string Name { get; }
  }

  [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
  public sealed class RazorInjectionAttribute : Attribute
  {
    public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName)
    {
      Type = type;
      FieldName = fieldName;
    }

    [NotNull] public string Type { get; }

    [NotNull] public string FieldName { get; }
  }

  [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
  public sealed class RazorDirectiveAttribute : Attribute
  {
    public RazorDirectiveAttribute([NotNull] string directive)
    {
      Directive = directive;
    }

    [NotNull] public string Directive { get; }
  }

  [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
  public sealed class RazorPageBaseTypeAttribute : Attribute
  {
      public RazorPageBaseTypeAttribute([NotNull] string baseType)
      {
        BaseType = baseType;
      }
      public RazorPageBaseTypeAttribute([NotNull] string baseType, string pageName)
      {
          BaseType = baseType;
          PageName = pageName;
      }

      [NotNull] public string BaseType { get; }
      [CanBeNull] public string PageName { get; }
  }

  [AttributeUsage(AttributeTargets.Method)]
  public sealed class RazorHelperCommonAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Property)]
  public sealed class RazorLayoutAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Method)]
  public sealed class RazorWriteLiteralMethodAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Method)]
  public sealed class RazorWriteMethodAttribute : Attribute { }

  [AttributeUsage(AttributeTargets.Parameter)]
  public sealed class RazorWriteMethodParameterAttribute : Attribute { }
}


================================================
FILE: App.xaml
================================================
<Application x:Class="fs2ff.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:local="clr-namespace:fs2ff"
             xmlns:ui="http://schemas.modernwpf.com/2019"
             StartupUri="MainWindow.xaml"
             mc:Ignorable="d">
    <Application.Resources>
        <ResourceDictionary x:Key="Resources">
            <local:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources.xaml" />
                <ui:ThemeResources RequestedTheme="Dark">
                    <ui:ThemeResources.ThemeDictionaries>
                        <ResourceDictionary x:Key="Dark">
                            <ResourceDictionary.MergedDictionaries>
                                <ui:ColorPaletteResources
                                    TargetTheme="Dark"
                                    Accent="Goldenrod"
                                    AltHigh="#FF232323" />
                            </ResourceDictionary.MergedDictionaries>
                        </ResourceDictionary>
                    </ui:ThemeResources.ThemeDictionaries>
                </ui:ThemeResources>
                <ui:XamlControlsResources />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>


================================================
FILE: App.xaml.cs
================================================
using System;
using System.Reflection;
using System.Windows;
using fs2ff.SimConnect;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace fs2ff
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App
    {
        private static readonly IHost Host = new HostBuilder()
            .ConfigureServices(ConfigureServices)
            .Build();

        protected override async void OnExit(ExitEventArgs e)
        {
            await Host.StopAsync(TimeSpan.FromSeconds(3));
            Host.Dispose();
            base.OnExit(e);
        }

        protected override async void OnStartup(StartupEventArgs e)
        {
            await Host.StartAsync();
            base.OnStartup(e);
        }

        public static Version AssemblyVersion => Assembly.GetEntryAssembly()!.GetName().Version!;

        public static string InformationalVersion => "v" + (Assembly
            .GetEntryAssembly()?
            .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
            .InformationalVersion ?? "0.0.0");

        public static T GetRequiredService<T>() where T : class => Host.Services.GetRequiredService<T>();

        public static void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<MainViewModel>();
            services.AddSingleton<SimConnectAdapter>();
            services.AddSingleton<DataSender>();
            services.AddSingleton<IpDetectionService>();
            services.AddHostedService(provider => provider.GetRequiredService<IpDetectionService>());
        }
    }
}


================================================
FILE: AssemblyInfo.cs
================================================
using System.Windows;

[assembly:ThemeInfo(
    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
                                     //(used if a resource is not found in the page,
                                     // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
                                              //(used if a resource is not found in the page,
                                              // app, or any theme specific resource dictionaries)
)]


================================================
FILE: Behaviors/MoveFocusOnEnterBehavior.cs
================================================
using System.Windows;
using System.Windows.Input;
using Microsoft.Xaml.Behaviors;

namespace fs2ff.Behaviors
{
    public class MoveFocusOnEnterBehavior : Behavior<UIElement>
    {
        protected override void OnAttached()
        {
            base.OnAttached();

            if (AssociatedObject != null)
                AssociatedObject.KeyDown += AssociatedObject_KeyDown;
        }

        protected override void OnDetaching()
        {
            if (AssociatedObject != null)
                AssociatedObject.KeyDown -= AssociatedObject_KeyDown;

            base.OnDetaching();
        }

        private static void AssociatedObject_KeyDown(object sender, KeyEventArgs e)
        {
            if (sender is UIElement element && e.Key == Key.Return)
                element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        }
    }
}


================================================
FILE: Behaviors/UpdateSourceOnLostFocusBehavior.cs
================================================
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Microsoft.Xaml.Behaviors;

namespace fs2ff.Behaviors
{
    public class UpdateSourceOnLostFocusBehavior : Behavior<TextBox>
    {
        protected override void OnAttached()
        {
            base.OnAttached();

            if (AssociatedObject != null)
                AssociatedObject.LostFocus += AssociatedObject_LostFocus;
        }

        protected override void OnDetaching()
        {
            if (AssociatedObject != null)
                AssociatedObject.LostFocus -= AssociatedObject_LostFocus;

            base.OnDetaching();
        }

        private static void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
        {
            if (sender is TextBox textBox)
                BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty)?.UpdateSource();
        }
    }
}


================================================
FILE: Converters/BooleanToDoubleConverter.cs
================================================
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Markup;

namespace fs2ff.Converters
{
    public class BooleanToDoubleConverter : MarkupExtension, IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is bool val))
                throw new InvalidOperationException($"{nameof(value)} must be a {nameof(Boolean)}");

            var doubles = parameter.ToString()?.Split(',').Select(double.Parse).ToArray() ?? new double[0];

            if (doubles.Length != 2)
                throw new InvalidOperationException($"{nameof(parameter)} must be two comma-separated integer values");

            return val
                ? doubles[0]
                : doubles[1];
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}


================================================
FILE: Converters/BooleanToVisibilityConverter.cs
================================================
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace fs2ff.Converters
{
    public class BooleanToVisibilityConverter : MarkupExtension, IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value is bool visible && visible
                ? Visibility.Visible
                : parameter is Visibility vis
                    ? vis
                    : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value is Visibility vis
                ? vis == Visibility.Visible
                : Binding.DoNothing;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}


================================================
FILE: Converters/FormatStringConverter.cs
================================================
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;

namespace fs2ff.Converters
{
    public class FormatStringConverter : MarkupExtension, IValueConverter
    {
        public object Convert(object value, Type targetType, object? parameter, CultureInfo culture)
        {
            return parameter is string format
                ? string.Format(format, value)
                : value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}


================================================
FILE: Converters/InvertedBooleanConverter.cs
================================================
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;

namespace fs2ff.Converters
{
    public class InvertedBooleanConverter : MarkupExtension, IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return !(bool)value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return !(bool)value;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}


================================================
FILE: Converters/IpAddressToStringConverter.cs
================================================
using System;
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows.Data;
using System.Windows.Markup;

namespace fs2ff.Converters
{
    public class IpAddressToStringConverter : MarkupExtension, IValueConverter
    {
        private readonly Regex _regex = new Regex(@"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");

        public object Convert(object? value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is IPAddress ip))
                return "";

            return Equals(ip, IPAddress.Any)
                ? ""
                : ip.ToString();
        }

        public object? ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return null;

            if (!(value is string str))
                return Binding.DoNothing;

            if (str == "")
                return null;

            if (!_regex.IsMatch(str))
                return Binding.DoNothing;

            return IPAddress.TryParse(str, out var result)
                ? result
                : Binding.DoNothing;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}


================================================
FILE: Converters/UIntToDoubleConverter.cs
================================================
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;

namespace fs2ff.Converters
{
    public class UIntToDoubleConverter : MarkupExtension, IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value is uint i
                ? (double)i
                : Binding.DoNothing;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value is double d
                ? (int)Math.Round(d)
                : Binding.DoNothing;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}


================================================
FILE: DataSender.cs
================================================
using System;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using fs2ff.Models;

namespace fs2ff
{
    public class DataSender : IDisposable
    {
        private const int Port = 49002;
        private const string SimId = "MSFS";

        private IPEndPoint? _endPoint;
        private Socket? _socket;

        public void Connect(IPAddress? ip)
        {
            Disconnect();

            ip ??= IPAddress.Broadcast;

            _endPoint = new IPEndPoint(ip, Port);
            _socket = new Socket(_endPoint.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
            {
                EnableBroadcast = ip.Equals(IPAddress.Broadcast)
            };
        }

        public void Disconnect() => _socket?.Dispose();

        public void Dispose() => _socket?.Dispose();

        public async Task Send(Attitude a)
        {
            var data = string.Format(CultureInfo.InvariantCulture,
                "XATT{0},{1:0.#},{2:0.#},{3:0.#},,,,,,,,,", // Garmin Pilot requires 13 fields
                SimId, a.TrueHeading, -a.Pitch, -a.Bank);

            await Send(data).ConfigureAwait(false);
        }

        public async Task Send(Position p)
        {
            var data = string.Format(CultureInfo.InvariantCulture,
                "XGPS{0},{1:0.#####},{2:0.#####},{3:0.#},{4:0.###},{5:0.#}",
                SimId, p.Longitude, p.Latitude, p.Altitude, p.GroundTrack, p.GroundSpeed);

            await Send(data).ConfigureAwait(false);
        }

        public async Task Send(Traffic t, uint id)
        {
            var data = string.Format(CultureInfo.InvariantCulture,
                "XTRAFFIC{0},{1},{2:0.#####},{3:0.#####},{4:0.#},{5:0.#},{6},{7:0.###},{8:0.#},{9}",
                SimId, id, t.Latitude, t.Longitude, t.Altitude, t.VerticalSpeed, t.OnGround ? 0 : 1,
                t.TrueHeading, t.GroundVelocity, TryGetFlightNumber(t) ?? t.TailNumber);

            await Send(data).ConfigureAwait(false);
        }

        private async Task Send(string data)
        {
            if (_endPoint != null && _socket != null)
            {
                await _socket
                    .SendToAsync(new ArraySegment<byte>(Encoding.ASCII.GetBytes(data)), SocketFlags.None, _endPoint)
                    .ConfigureAwait(false);
            }
        }

        private static string? TryGetFlightNumber(Traffic t) =>
            !string.IsNullOrEmpty(t.Airline) && !string.IsNullOrEmpty(t.FlightNumber)
                ? $"{t.Airline} {t.FlightNumber}"
                : null;
    }
}


================================================
FILE: Extensions.cs
================================================
using System;
using System.Threading.Tasks;

namespace fs2ff
{
    public static class Extensions
    {
        public static T AdjustToBounds<T>(this T value, T min, T max) where T : IComparable =>
            value.CompareTo(min) < 0
                ? min
                : value.CompareTo(max) > 0
                    ? max
                    : value;

        public static async Task RaiseAsync<T>(this Func<T, Task>? handler, T value)
        {
            if (handler == null)
            {
                return;
            }

            Delegate[] delegates = handler.GetInvocationList();
            Task[] tasks = new Task[delegates.Length];

            for (var i = 0; i < delegates.Length; i++)
            {
                tasks[i] = ((Func<T, Task>) delegates[i])(value);
            }

            await Task.WhenAll(tasks);
        }

        public static async Task RaiseAsync<T1, T2>(this Func<T1, T2, Task>? handler, T1 value1, T2 value2)
        {
            if (handler == null)
            {
                return;
            }

            Delegate[] delegates = handler.GetInvocationList();
            Task[] tasks = new Task[delegates.Length];

            for (var i = 0; i < delegates.Length; i++)
            {
                tasks[i] = ((Func<T1, T2, Task>) delegates[i])(value1, value2);
            }

            await Task.WhenAll(tasks);
        }
    }
}


================================================
FILE: FodyWeavers.xml
================================================
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <PropertyChanged />
</Weavers>

================================================
FILE: FodyWeavers.xsd
================================================
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
  <xs:element name="Weavers">
    <xs:complexType>
      <xs:all>
        <xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
          <xs:complexType>
            <xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
              <xs:annotation>
                <xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
            <xs:attribute name="TriggerDependentProperties" type="xs:boolean">
              <xs:annotation>
                <xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
            <xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
              <xs:annotation>
                <xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
            <xs:attribute name="EventInvokerNames" type="xs:string">
              <xs:annotation>
                <xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
            <xs:attribute name="CheckForEquality" type="xs:boolean">
              <xs:annotation>
                <xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
            <xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
              <xs:annotation>
                <xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
            <xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
              <xs:annotation>
                <xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
            <xs:attribute name="SuppressWarnings" type="xs:boolean">
              <xs:annotation>
                <xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
            <xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
              <xs:annotation>
                <xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
              </xs:annotation>
            </xs:attribute>
          </xs:complexType>
        </xs:element>
      </xs:all>
      <xs:attribute name="VerifyAssembly" type="xs:boolean">
        <xs:annotation>
          <xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="VerifyIgnoreCodes" type="xs:string">
        <xs:annotation>
          <xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="GenerateXsd" type="xs:boolean">
        <xs:annotation>
          <xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
</xs:schema>

================================================
FILE: IpDetectionService.cs
================================================
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;

namespace fs2ff
{
    public class IpDetectionService : IHostedService
    {
        private const int Port = 63093;

        public event Action<IPAddress>? NewIpDetected;

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            await Task.Run(async () =>
            {
                try
                {
                    using var udpClient = new UdpClient(Port, IPAddress.Any.AddressFamily);

                    while (!cancellationToken.IsCancellationRequested)
                    {
                        var result = await udpClient.ReceiveAsync();
                        var text = Encoding.ASCII.GetString(result.Buffer);

                        if (IsForeFlightGdl90(text))
                        {
                            NewIpDetected?.Invoke(result.RemoteEndPoint.Address);
                        }
                    }
                }
                catch (SocketException e)
                {
                    Console.Error.WriteLine(e);
                }
            }, cancellationToken);
        }

        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;

        private static bool IsForeFlightGdl90(string text)
        {
            try
            {
                return JsonDocument.Parse(text).RootElement.TryGetProperty("App", out var app) &&
                       app.GetString() == "ForeFlight";
            }
            catch (Exception)
            {
                return false;
            }
        }
    }
}


================================================
FILE: LICENSE
================================================
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <https://unlicense.org>


================================================
FILE: MainViewModel.cs
================================================
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using fs2ff.Models;
using fs2ff.SimConnect;

#pragma warning disable 67

namespace fs2ff
{
    [SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "DispatcherTimer field is kept to prevent premature GC")]
    public class MainViewModel : INotifyPropertyChanged, ISimConnectMessageHandler
    {
        private readonly DataSender _dataSender;
        private readonly SimConnectAdapter _simConnect;
        private readonly IpDetectionService _ipDetectionService;
        private readonly DispatcherTimer _ipHintTimer;
        private readonly DispatcherTimer _autoConnectTimer;

        private uint _attitudeFrequency = Preferences.Default.att_freq.AdjustToBounds(AttitudeFrequencyMin, AttitudeFrequencyMax);
        private bool _autoDetectIpEnabled = Preferences.Default.ip_detection_enabled;
        private bool _autoConnectEnabled = Preferences.Default.auto_connect_enabled;
        private bool _dataAttitudeEnabled = Preferences.Default.att_enabled;
        private bool _dataPositionEnabled = Preferences.Default.pos_enabled;
        private bool _dataTrafficEnabled = Preferences.Default.tfk_enabled;
        private bool _errorOccurred;
        private IntPtr _hwnd = IntPtr.Zero;
        private IPAddress? _ipAddress;
        private uint _ipHintMinutesLeft = Preferences.Default.ip_hint_time;

        public MainViewModel(DataSender dataSender, SimConnectAdapter simConnect, IpDetectionService ipDetectionService)
        {
            _dataSender = dataSender;

            _simConnect = simConnect;
            _simConnect.StateChanged += SimConnectStateChanged;
            _simConnect.PositionReceived += SimConnectPositionReceived;
            _simConnect.AttitudeReceived += SimConnectAttitudeReceived;
            _simConnect.TrafficReceived += SimConnectTrafficReceived;

            _ipDetectionService = ipDetectionService;
            _ipDetectionService.NewIpDetected += IpDetectionService_NewIpDetected;

            OpenSettingsCommand = new ActionCommand(OpenSettings);
            DismissSettingsPaneCommand = new ActionCommand(DismissSettingsPane);
            GotoNewReleaseCommand = new ActionCommand(GotoReleaseNotesPage);
            ToggleConnectCommand = new ActionCommand(ToggleConnect, CanConnect);
            ToggleSettingsPaneCommand = new ActionCommand(ToggleSettingsPane);

            _ipAddress = IPAddress.TryParse(Preferences.Default.ip_address, out var ip) ? ip : null;

            _ipHintTimer = new DispatcherTimer(TimeSpan.FromMinutes(1), DispatcherPriority.Normal, IpHintCallback, Dispatcher.CurrentDispatcher);
            _autoConnectTimer = new DispatcherTimer(TimeSpan.FromSeconds(5), DispatcherPriority.Normal, AutoConnectCallback, Dispatcher.CurrentDispatcher);

            ManageAutoConnect();
            CheckForUpdates();
            UpdateVisualState();
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        [SuppressMessage("ReSharper", "UnusedMember.Local", Justification = "Unknown is not supposed to be used")]
        private enum FlightSimState
        {
            Unknown,
            Connected,
            Disconnected,
            ErrorOccurred,
            AutoConnecting
        }

        public static string WindowTitle => $"fs2ff - {App.InformationalVersion}";

        public uint AttitudeFrequency
        {
            get => _attitudeFrequency;
            set
            {
                _attitudeFrequency = value.AdjustToBounds(AttitudeFrequencyMin, AttitudeFrequencyMax);
                _simConnect.SetAttitudeFrequency(_attitudeFrequency);
                Preferences.Default.att_freq = value;
                Preferences.Default.Save();
            }
        }

        public static uint AttitudeFrequencyMax => 10;

        public static uint AttitudeFrequencyMin => 4;

        public bool AutoConnectEnabled
        {
            get => _autoConnectEnabled;
            set
            {
                if (value != _autoConnectEnabled)
                {
                    _autoConnectEnabled = value;
                    Preferences.Default.auto_connect_enabled = value;
                    Preferences.Default.Save();

                    // If auto connect was running and the sim wasn't then there is likely
                    // an error flagged. Clear it whenever AutoConnectEnabled changes state
                    // so in the event auto connect is disabled the window won't show
                    // a meaningless connection error to the user.
                    _errorOccurred = false;

                    ManageAutoConnect();
                    UpdateVisualState();
                }
            }
        }

        public bool AutoConnectLabelVisible { get; private set; }

        public bool AutoDetectIpEnabled
        {
            get => _autoDetectIpEnabled;
            set
            {
                if (value != _autoDetectIpEnabled)
                {
                    _autoDetectIpEnabled = value;
                    Preferences.Default.ip_detection_enabled = value;
                    Preferences.Default.Save();
                }
            }
        }

        public bool ConnectButtonEnabled { get => !_autoConnectEnabled; }

        public string? ConnectButtonText { get; private set; }

        public bool ConnectedLabelVisible { get; private set; }

        public bool DataAttitudeEnabled
        {
            get => _dataAttitudeEnabled;
            set
            {
                if (value != _dataAttitudeEnabled)
                {
                    _dataAttitudeEnabled = value;
                    Preferences.Default.att_enabled = value;
                    Preferences.Default.Save();
                }
            }
        }

        public bool DataPositionEnabled
        {
            get => _dataPositionEnabled;
            set
            {
                if (value != _dataPositionEnabled)
                {
                    _dataPositionEnabled = value;
                    Preferences.Default.pos_enabled = value;
                    Preferences.Default.Save();
                }
            }
        }

        public bool DataTrafficEnabled
        {
            get => _dataTrafficEnabled;
            set
            {
                if (value != _dataTrafficEnabled)
                {
                    _dataTrafficEnabled = value;
                    Preferences.Default.tfk_enabled = value;
                    Preferences.Default.Save();
                }
            }
        }

        public ICommand DismissSettingsPaneCommand { get; }

        public bool ErrorLabelVisible { get; private set; }

        public ICommand GotoNewReleaseCommand { get; }

        public bool IndicatorVisible { get; private set; }

        public IPAddress? IpAddress
        {
            get => _ipAddress;
            set
            {
                if (!Equals(value, _ipAddress))
                {
                    _ipAddress = value;
                    Preferences.Default.ip_address = value?.ToString() ?? "";
                    Preferences.Default.Save();

                    if (value != null)
                    {
                        ResetIpHintMinutesLeft();
                    }

                    ResetDataSenderConnection();
                }
            }
        }

        public bool IpHintVisible => IpAddress == null && IpHintMinutesLeft == 0;

        public bool NotLabelVisible { get; private set; }

        public ICommand OpenSettingsCommand { get; }

        public bool SettingsPaneVisible { get; set; }

        public ActionCommand ToggleConnectCommand { get; }

        public ICommand ToggleSettingsPaneCommand { get; }

        public bool UpdateMsgVisible => UpdateInfo != null && !SettingsPaneVisible;

        public IntPtr WindowHandle
        {
            get => _hwnd;
            set
            {
                _hwnd = value;
                ToggleConnectCommand.TriggerCanExecuteChanged();
            }
        }

        private FlightSimState CurrentFlightSimState =>
            _simConnect.Connected ? FlightSimState.Connected :
            AutoConnectEnabled ? FlightSimState.AutoConnecting :
            _errorOccurred ? FlightSimState.ErrorOccurred :
            FlightSimState.Disconnected;

        private uint IpHintMinutesLeft
        {
            get => _ipHintMinutesLeft;
            set
            {
                if (value != _ipHintMinutesLeft)
                {
                    _ipHintMinutesLeft = value;
                    Preferences.Default.ip_hint_time = value;
                    Preferences.Default.Save();
                }
            }
        }

        private UpdateInformation? UpdateInfo { get; set; }

        public void Dispose()
        {
            _ipDetectionService.NewIpDetected -= IpDetectionService_NewIpDetected;

            _simConnect.TrafficReceived -= SimConnectTrafficReceived;
            _simConnect.AttitudeReceived -= SimConnectAttitudeReceived;
            _simConnect.PositionReceived -= SimConnectPositionReceived;
            _simConnect.StateChanged -= SimConnectStateChanged;
            _simConnect.Dispose();

            _dataSender.Dispose();
        }

        public void ReceiveFlightSimMessage() => _simConnect.ReceiveMessage();

        private void AutoConnectCallback(object? sender, EventArgs e) => Connect();

        private bool CanConnect() => WindowHandle != IntPtr.Zero;

        private void CheckForUpdates()
        {
            UpdateChecker.Check().ContinueWith(task => UpdateInfo = task.Result, TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void Connect() => _simConnect.Connect(WindowHandle, AttitudeFrequency);

        private void Disconnect() => _simConnect.Disconnect();

        private void DismissSettingsPane() => SettingsPaneVisible = false;


        private void GotoReleaseNotesPage()
        {
            if (UpdateInfo != null)
            {
                Process.Start("explorer.exe", UpdateInfo.DownloadLink.ToString());
            }
        }

        private void IpDetectionService_NewIpDetected(IPAddress ip)
        {
            if (AutoDetectIpEnabled)
            {
                IpAddress = ip;
            }
        }

        private void IpHintCallback(object? sender, EventArgs e)
        {
            if (IpHintMinutesLeft > 0 && IpAddress == null && _simConnect.Connected)
            {
                IpHintMinutesLeft--;
            }
        }

        private void ManageAutoConnect()
        {
            if (!AutoConnectEnabled || CurrentFlightSimState == FlightSimState.Connected)
            {
                _autoConnectTimer.Stop();
            }
            else
            {
                _autoConnectTimer.Start();
            }
        }

        private void OpenSettings() => SettingsPaneVisible = true;

        private void ResetDataSenderConnection()
        {
            if (CurrentFlightSimState == FlightSimState.Connected)
            {
                _dataSender.Connect(IpAddress);
            }
            else
            {
                _dataSender.Disconnect();
            }
        }

        private void ResetIpHintMinutesLeft()
        {
            Preferences.Default.PropertyValues["ip_hint_time"].SerializedValue = Preferences.Default.Properties["ip_hint_time"].DefaultValue;
            Preferences.Default.PropertyValues["ip_hint_time"].Deserialized = false;
            IpHintMinutesLeft = Preferences.Default.ip_hint_time;
        }

        private async Task SimConnectAttitudeReceived(Attitude att)
        {
            if (DataAttitudeEnabled)
            {
                await _dataSender.Send(att).ConfigureAwait(false);
            }
        }

        private async Task SimConnectPositionReceived(Position pos)
        {
            if (DataPositionEnabled && (pos.Latitude != 0d || pos.Longitude != 0d))
            {
                await _dataSender.Send(pos).ConfigureAwait(false);
            }
        }

        private void SimConnectStateChanged(bool failure)
        {
            _errorOccurred = failure;

            ManageAutoConnect();
            ResetDataSenderConnection();
            UpdateVisualState();
        }

        private async Task SimConnectTrafficReceived(Traffic tfk, uint id)
        {
            // Ignore traffic with id=1, that's our own aircraft
            if (DataTrafficEnabled && id != 1)
            {
                await _dataSender.Send(tfk, id).ConfigureAwait(false);
            }
        }

        private void ToggleConnect()
        {
            if (_simConnect.Connected) Disconnect();
            else                          Connect();
        }

        private void ToggleSettingsPane() => SettingsPaneVisible = !SettingsPaneVisible;

        private void UpdateVisualState()
        {
            (IndicatorVisible, AutoConnectLabelVisible, NotLabelVisible, ErrorLabelVisible, ConnectedLabelVisible, ConnectButtonText) = CurrentFlightSimState switch
            {
                FlightSimState.AutoConnecting => (false, true, false, false, false, "Connect"),
                FlightSimState.Connected => (true, false, false, false, true, "Disconnect"),
                FlightSimState.Disconnected => (false, false, true, false, true, "Connect"),
                FlightSimState.ErrorOccurred => (false, false, false, true, false, "Connect"),
                _ => (false, false, true, false, true, "Connect")
            };
        }
    }
}


================================================
FILE: MainWindow.xaml
================================================
<Window x:Class="fs2ff.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ui="http://schemas.modernwpf.com/2019"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:b="clr-namespace:fs2ff.Behaviors"
        xmlns:c="clr-namespace:fs2ff.Converters"
        ui:WindowHelper.UseModernWindowStyle="True"
        mc:Ignorable="d"
        Title="{Binding WindowTitle}"
        Icon="img\icon.ico"
        Width="555" Height="430"
        MinWidth="555" MinHeight="430"
        Closing="Window_Closing"
        Loaded="Window_Loaded"
        DataContext="{Binding Main, Source={StaticResource Locator}}">
    <Window.Resources>
        <Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="FontFamily" Value="Segoe UI" />
            <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        </Style>
        <Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
            <Setter Property="Focusable" Value="False" />
            <Setter Property="FontFamily" Value="Segoe UI" />
            <Setter Property="FontSize" Value="12" />
        </Style>
        <Style TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
            <Setter Property="Focusable" Value="False" />
            <Setter Property="FontFamily" Value="Segoe UI" />
            <Setter Property="FontSize" Value="12" />
        </Style>
        <Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
            <Setter Property="FontFamily" Value="Segoe UI" />
            <Setter Property="FontSize" Value="12" />
            <Setter Property="FontWeight" Value="SemiBold" />
            <Setter Property="Foreground" Value="DarkGray" />
        </Style>
        <Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
            <Setter Property="FontFamily" Value="Segoe UI" />
            <Setter Property="FontSize" Value="12" />
        </Style>
        <Style TargetType="ToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
            <Setter Property="Focusable" Value="False" />
            <Setter Property="FontFamily" Value="Segoe UI" />
            <Setter Property="FontSize" Value="12" />
        </Style>
    </Window.Resources>
    <Window.InputBindings>
        <KeyBinding Key="Tab" Command="{Binding ToggleSettingsPaneCommand}" />
    </Window.InputBindings>
    <ui:SplitView
        FocusManager.FocusedElement="{Binding ElementName=ConnectButton}"
        IsPaneOpen="{Binding SettingsPaneVisible}"
        DisplayMode="Inline"
        OpenPaneLength="185"
        PanePlacement="Right">
        <i:Interaction.Triggers>
            <i:KeyTrigger Key="Escape">
                <i:Interaction.Behaviors>
                    <i:ConditionBehavior>
                        <i:ConditionalExpression>
                            <i:ComparisonCondition
                                LeftOperand="{
                                    Binding IsPaneOpen,
                                    RelativeSource={RelativeSource FindAncestor, AncestorType=ui:SplitView}}"
                                RightOperand="True" />
                        </i:ConditionalExpression>
                    </i:ConditionBehavior>
                </i:Interaction.Behaviors>
                <i:InvokeCommandAction Command="{Binding DismissSettingsPaneCommand}" />
            </i:KeyTrigger>
        </i:Interaction.Triggers>
        <ui:SplitView.Pane>
            <StackPanel
                Orientation="Vertical"
                Margin="15,0,15,0">
                <CheckBox Margin="0,20,0,5" 
                    IsChecked="{Binding AutoConnectEnabled, Mode=TwoWay}"
                    Content="Auto connect"
                    ToolTip="Automatically connects to Flight Simulator when it launches" />
                <Label
                    Content="DEVICE IP"
                    Margin="0,20,0,5" />
                <CheckBox
                    IsChecked="{Binding AutoDetectIpEnabled, Mode=TwoWay}"
                    Content="Auto-detect*"
                    ToolTip="Requires ForeFlight running in the foreground" />
                <TextBox
                    Text="{
                        Binding IpAddress, Mode=TwoWay,
                        Converter={c:IpAddressToStringConverter},
                        UpdateSourceTrigger=PropertyChanged, Delay=5000}"
                    Template="{StaticResource WatermarkTextBoxTemplate}"
                    Tag="255.255.255.255"
                    Margin="0,10,0,0">
                    <i:Interaction.Behaviors>
                        <b:MoveFocusOnEnterBehavior />
                        <b:UpdateSourceOnLostFocusBehavior />
                    </i:Interaction.Behaviors>
                </TextBox>
                <Label
                    Content="DATA SELECTION"
                    Margin="0,20,0,5" />
                <CheckBox
                    IsChecked="{Binding DataPositionEnabled, Mode=TwoWay}"
                    Content="Position" />
                <CheckBox
                    IsChecked="{Binding DataTrafficEnabled, Mode=TwoWay}"
                    Content="Traffic" />
                <CheckBox
                    IsChecked="{Binding DataAttitudeEnabled, Mode=TwoWay}"
                    Content="Attitude" />
                <Label
                    Content="{Binding Value, ElementName=Slider, Converter={c:FormatStringConverter}, ConverterParameter='ATTITUDE FREQ: {0} Hz'}"
                    Margin="0,14,0,5" />
                <Slider
                    Name="Slider"
                    IsEnabled="{Binding DataAttitudeEnabled}"
                    Value="{Binding AttitudeFrequency, Mode=TwoWay, Delay=2000, Converter={c:UIntToDoubleConverter}}"
                    Minimum="{Binding AttitudeFrequencyMin, Converter={c:UIntToDoubleConverter}}"
                    Maximum="{Binding AttitudeFrequencyMax, Converter={c:UIntToDoubleConverter}}"
                    Focusable="False"
                    TickPlacement="Both"
                    IsSnapToTickEnabled="True" />
            </StackPanel>
        </ui:SplitView.Pane>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="40" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Label
                Grid.Row="0"
                Grid.Column="0"
                Visibility="{Binding IpHintVisible, Converter={c:BooleanToVisibilityConverter}}"
                HorizontalAlignment="Center"
                VerticalAlignment="Top"
                FontStyle="Italic"
                Margin="0,30,0,0">
                <StackPanel Orientation="Vertical">
                    <TextBlock HorizontalAlignment="Center">
                        Device IP not set. Please set it manually or enable
                    </TextBlock>
                    <TextBlock HorizontalAlignment="Center">
                        auto-detect for improved performance.
                        <Hyperlink Command="{Binding OpenSettingsCommand}">Open settings.</Hyperlink>
                    </TextBlock>
                </StackPanel>
            </Label>
            <Button
                Grid.Row="0"
                Grid.Column="0"
                Grid.RowSpan="2"
                Name="ConnectButton"
                Content="{Binding ConnectButtonText, FallbackValue='Connect'}"
                Command="{Binding ToggleConnectCommand}"
                FontSize="16"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Width="144"
                Height="60"
                IsEnabled="{Binding ConnectButtonEnabled}" />
            <Ellipse
                Grid.Row="1"
                Grid.Column="0"
                Fill="LawnGreen"
                Stroke="ForestGreen"
                StrokeThickness="1"
                Width="10" Height="10"
                VerticalAlignment="Center"
                HorizontalAlignment="Left"
                Visibility="{Binding IndicatorVisible, Converter={c:BooleanToVisibilityConverter}}"
                Margin="27,0,0,0" />
            <Label
                Grid.Row="1"
                Grid.Column="0"
                Content="NOT"
                Foreground="DarkGray"
                VerticalAlignment="Center"
                HorizontalAlignment="Left"
                Visibility="{Binding NotLabelVisible, Converter={c:BooleanToVisibilityConverter}}"
                Margin="17,0,0,0" />
            <Label
                Grid.Row="1"
                Grid.Column="0"
                Content="CONNECTED"
                Foreground="DarkGray"
                VerticalAlignment="Center"
                HorizontalAlignment="Left"
                Visibility="{Binding ConnectedLabelVisible, Converter={c:BooleanToVisibilityConverter}}"
                Margin="45,0,0,0" />
            <Label
                Grid.Row="1"
                Grid.Column="0"
                Content="AUTO CONNECTING..."
                Foreground="DarkGray"
                VerticalAlignment="Center"
                HorizontalAlignment="Left"
                Visibility="{Binding AutoConnectLabelVisible, Converter={c:BooleanToVisibilityConverter}}"
                Margin="10,0,0,0" />
            <Label
                Grid.Row="1"
                Grid.Column="0"
                Content="UNABLE TO CONNECT TO FLIGHT SIMULATOR"
                Foreground="OrangeRed"
                VerticalAlignment="Center"
                HorizontalAlignment="Left"
                Visibility="{Binding ErrorLabelVisible, Converter={c:BooleanToVisibilityConverter}}"
                Margin="17,0,0,0" />
            <Label
                Grid.Row="1"
                Grid.Column="0"
                Visibility="{Binding UpdateMsgVisible, Converter={c:BooleanToVisibilityConverter}}"
                VerticalAlignment="Center"
                HorizontalAlignment="Right"
                FontStyle="Italic"
                Margin="15,0,25,0">
                <Hyperlink Command="{Binding GotoNewReleaseCommand}">Update available</Hyperlink>
            </Label>
            <ToggleButton
                Grid.Row="0"
                Grid.Column="1"
                Grid.RowSpan="2"
                IsChecked="{Binding SettingsPaneVisible, Mode=TwoWay}"
                VerticalAlignment="Stretch"
                Width="47">
                <ToggleButton.ContentTemplate>
                    <DataTemplate>
                        <Label Content="S   E   T   T   I   N   G   S" Foreground="White">
                            <Label.LayoutTransform>
                                <TransformGroup>
                                    <RotateTransform x:Name="RotateTransform" Angle="90" />
                                </TransformGroup>
                            </Label.LayoutTransform>
                            <i:Interaction.Triggers>
                                <i:PropertyChangedTrigger Binding="{
                                    Binding IsPaneOpen,
                                    RelativeSource={
                                        RelativeSource FindAncestor,
                                        AncestorType=ui:SplitView}}">
                                    <i:ChangePropertyAction
                                        TargetName="RotateTransform"
                                        PropertyName="Angle">
                                        <i:ChangePropertyAction.Value>
                                            <Binding
                                                Path="IsPaneOpen"
                                                RelativeSource="{
                                                    RelativeSource FindAncestor,
                                                    AncestorType=ui:SplitView}"
                                                Converter="{c:BooleanToDoubleConverter}"
                                                ConverterParameter="270,90" />
                                        </i:ChangePropertyAction.Value>
                                    </i:ChangePropertyAction>
                                </i:PropertyChangedTrigger>
                            </i:Interaction.Triggers>
                        </Label>
                    </DataTemplate>
                </ToggleButton.ContentTemplate>
            </ToggleButton>
        </Grid>
    </ui:SplitView>
</Window>


================================================
FILE: MainWindow.xaml.cs
================================================
// ReSharper disable InconsistentNaming

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Interop;
using fs2ff.SimConnect;

namespace fs2ff
{
    public partial class MainWindow
    {
        private const uint WM_USER_SIMCONNECT = 0x0402;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            ((ISimConnectMessageHandler) DataContext).Dispose();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            HwndSource hwndSource = (HwndSource) PresentationSource.FromVisual(this)!;
            hwndSource.AddHook(WndProc);
            ((ISimConnectMessageHandler) DataContext).WindowHandle = hwndSource.Handle;
        }

        private IntPtr WndProc(IntPtr hWnd, int iMsg, IntPtr hWParam, IntPtr hLParam, ref bool bHandled)
        {
            if (iMsg == WM_USER_SIMCONNECT)
            {
                ((ISimConnectMessageHandler) DataContext).ReceiveFlightSimMessage();
            }

            return IntPtr.Zero;
        }
    }
}


================================================
FILE: Models/Attitude.cs
================================================
// ReSharper disable FieldCanBeMadeReadOnly.Global

using System.Runtime.InteropServices;

namespace fs2ff.Models
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct Attitude
    {
        public double Pitch;
        public double Bank;
        public double TrueHeading;
    }
}


================================================
FILE: Models/Position.cs
================================================
// ReSharper disable FieldCanBeMadeReadOnly.Global

using System.Runtime.InteropServices;

namespace fs2ff.Models
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct Position
    {
        public double Latitude;
        public double Longitude;
        public double Altitude;
        public double GroundTrack;
        public double GroundSpeed;
    }
}


================================================
FILE: Models/Traffic.cs
================================================
// ReSharper disable FieldCanBeMadeReadOnly.Global

using System.Runtime.InteropServices;

namespace fs2ff.Models
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct Traffic
    {
        public double Latitude;
        public double Longitude;
        public double Altitude;
        public double VerticalSpeed;
        public bool OnGround;
        public double TrueHeading;
        public double GroundVelocity;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string TailNumber;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string Airline;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
        public string FlightNumber;
    }
}


================================================
FILE: Preferences.Designer.cs
================================================
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace fs2ff {
    
    
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")]
    internal sealed partial class Preferences : global::System.Configuration.ApplicationSettingsBase {
        
        private static Preferences defaultInstance = ((Preferences)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Preferences())));
        
        public static Preferences Default {
            get {
                return defaultInstance;
            }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("")]
        public string ip_address {
            get {
                return ((string)(this["ip_address"]));
            }
            set {
                this["ip_address"] = value;
            }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("True")]
        public bool att_enabled {
            get {
                return ((bool)(this["att_enabled"]));
            }
            set {
                this["att_enabled"] = value;
            }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("True")]
        public bool pos_enabled {
            get {
                return ((bool)(this["pos_enabled"]));
            }
            set {
                this["pos_enabled"] = value;
            }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("True")]
        public bool tfk_enabled {
            get {
                return ((bool)(this["tfk_enabled"]));
            }
            set {
                this["tfk_enabled"] = value;
            }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("True")]
        public bool ip_detection_enabled {
            get {
                return ((bool)(this["ip_detection_enabled"]));
            }
            set {
                this["ip_detection_enabled"] = value;
            }
        }

        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("True")]
        public bool auto_connect_enabled {
            get {
                return ((bool)(this["auto_connect_enabled"]));
            }
            set {
                this["auto_connect_enabled"] = value;
            }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("6")]
        public uint att_freq {
            get {
                return ((uint)(this["att_freq"]));
            }
            set {
                this["att_freq"] = value;
            }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("10")]
        public uint ip_hint_time {
            get {
                return ((uint)(this["ip_hint_time"]));
            }
            set {
                this["ip_hint_time"] = value;
            }
        }
    }
}


================================================
FILE: Preferences.settings
================================================
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="fs2ff" GeneratedClassName="Preferences">
  <Profiles />
  <Settings>
    <Setting Name="ip_address" Type="System.String" Scope="User">
      <Value Profile="(Default)" />
    </Setting>
    <Setting Name="att_enabled" Type="System.Boolean" Scope="User">
      <Value Profile="(Default)">True</Value>
    </Setting>
    <Setting Name="pos_enabled" Type="System.Boolean" Scope="User">
      <Value Profile="(Default)">True</Value>
    </Setting>
    <Setting Name="tfk_enabled" Type="System.Boolean" Scope="User">
      <Value Profile="(Default)">True</Value>
    </Setting>
    <Setting Name="ip_detection_enabled" Type="System.Boolean" Scope="User">
      <Value Profile="(Default)">True</Value>
    </Setting>
    <Setting Name="att_freq" Type="System.UInt32" Scope="User">
      <Value Profile="(Default)">6</Value>
    </Setting>
    <Setting Name="ip_hint_time" Type="System.UInt32" Scope="User">
      <Value Profile="(Default)">10</Value>
    </Setting>
    <Setting Name="auto_connect_enabled" Type="System.Boolean" Scope="User">
      <Value Profile="(Default)">True</Value>
    </Setting>
  </Settings>
</SettingsFile>

================================================
FILE: README.md
================================================
# fs2ff (Flight Simulator to ForeFlight)

## What is it?

This is a utility app that connects Microsoft Flight Simulator 2020 with the Electronic Flight Bag (EFB) app ForeFlight, sharing virtual GPS data from the former to the latter. This allows the use of ForeFlight as a navigational aid when simming. Much more cost-effective than flying an actual airplane!

## How do I use it?

Simple! Just follow these easy steps:
1. Start Microsoft Flight Simulator. As soon as you get to the main menu, the game is ready to accept connections.
1. While waiting for MSFS to start (it takes a while!), download fs2ff.exe from the [latest release](https://github.com/astenlund/fs2ff/releases/latest).
1. Double-click the file.
1. If you get a popup window that tells you that "Windows protected your PC", click More info -> Run anyway.
1. In the fs2ff app, click the big Connect button. Unless anything goes wrong, you will now be connected to MSFS.
1. To verify the connection, open ForeFlight on your iPad or iPhone and navigate to More -> Devices.
1. Do not forget to activate Auto Center (upper right corner) in the map view in ForeFlight.
1. Now go fly!

__N.B.__ Since I have not bothered to create an installer for this app, you will just have to save it somewhere on your harddrive where you can find it again. Why not your desktop? ;)

## Ok, but what does it actually do?

The app uses Microsoft's SimConnect SDK to continuously collect data about the player's own aircraft in the game, as well as other traffic around the player, and then broadcast these data so they can be picked up by ForeFlight. This requires that the ForeFlight device is connected to the same Wi-Fi network as the Flight Sim computer. Also, UDP port 49002 must not be blocked by any firewalls.

## Does it work with other EFB apps?

### SkyDemon

Yes! SkyDemon is compatible with X-Plane, which uses the same format. You will need to enable the "X-Plane" toggle in the "Third Party Devices" settings pane under the cogwheel in SkyDemon. Then, when you start flying, select "Use X-Plane".

### Garmin Pilot

Yes! Go to "Settings" -> "Flight Simulation" and enable the "Use Flight Simulator Data" option.

### Enroute Flight Navigation

Yes! Go to "Settings" -> "Primary position data source" and select "Traffic data receiver" as the main source. More information can be found in the [Enroute Flight Navigation Manual](https://akaflieg-freiburg.github.io/enrouteManual/02-tutorialBasic/07-simulator.html#connect-your-flight-simulator).

### Other apps (not verified by me)

- FlyQ EFB (thanks, @erayymz)
- FltPlan GO (need to select XPlane as source of GPS data)

## Does it work with other flight simulators?

### X-Plane

No need for my app. X-Plane already has this broadcast capability built-in, see [this support page](https://foreflight.com/support/support-center/category/about-foreflight-mobile/204115525).

### Prepar3D

Should work straight out of the box for P3D 4.0 and up, so no need for my app. For older versions, use [FSUIPC](http://www.schiratti.com/dowson.html). See [this support page](https://foreflight.com/support/support-center/category/about-foreflight-mobile/204115345).

### Other flight sims

As of now, no other flight simulators have been tested.

## How do I build this?

1. Download and install [.NET Core SDK](https://dotnet.microsoft.com/download) and [Visual Studio Community](https://visualstudio.microsoft.com/downloads/).
1. Clone the repo or download and extract [a zip](https://github.com/astenlund/fs2ff/archive/master.zip).
1. Install MSFS SDK (see instructions below).
1. Navigate to the SDK on your hard drive and find the following two files:
   - "MSFS SDK\SimConnect SDK\lib\SimConnect.dll"
   - "MSFS SDK\SimConnect SDK\lib\managed\Microsoft.FlightSimulator.SimConnect.dll"
1. Create a folder called "lib" in the fs2ff folder (next to fs2ff.sln) and put the two dll:s therein.
1. Open fs2ff.sln with Visual Studio.
1. Build by pressing Ctrl-Shift-B.
1. Or from command-line: `dotnet build`.
1. To build a self-contained executable, run: `dotnet publish -c Release -r win-x64 /p:PublishSingleFile=true`.

## Where do I get the MSFS SDK?

1. Hop into Flight Simulator.
1. Go to OPTIONS -> GENERAL -> DEVELOPERS and enable DEVELOPER MODE.
1. You will now have a new menu at the top. Click Help -> SDK Installer.
1. Let your browser download the installer and run it.
1. You might get a "Windows protected your PC" popup. If so, click More info -> Run anyway.
1. Go through the installation wizard and make sure that Core Components is selected.
1. When finished, you will likely find the SDK installed under "C:\MSFS SDK".

## What's with the "Windows protected your PC" popup?

This is Microsoft telling you that the app has not been cryptographically signed. Software that you download from big corporations does not present this behaviour, because these companies typically purchase certificates from trusted Certificate Authorities and sign their binaries before shipping to customers. This is cumbersome and expensive and not in the scope of this open source project. The binaries that I have provided are for convenience. If you do not trust me (and why should you?), you are more than welcome to build from source yourself (see instructions above).

## I have problems!

If you are experiencing any technical issues with the app (or if you see potential for other improvements), please open up an [issue](https://github.com/astenlund/fs2ff/issues), and I will be happy to take a look.


================================================
FILE: Resources.xaml
================================================
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ControlTemplate TargetType="TextBox" x:Key="WatermarkTextBoxTemplate">
        <Grid>
            <TextBox Text="{
                    Binding Text, Mode=TwoWay,
                    RelativeSource={RelativeSource TemplatedParent},
                    UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock
                HorizontalAlignment="Left"
                VerticalAlignment="Center"
                Text="{Binding Tag, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
                Margin="10,0"
                Foreground="#FF808080"
                FontStyle="Italic"
                IsHitTestVisible="False"
                x:Name="Watermark"
                Visibility="Hidden" />
        </Grid>
        <ControlTemplate.Triggers>
            <MultiTrigger>
                <Setter Property="Visibility" TargetName="Watermark" Value="Visible" />
                <MultiTrigger.Conditions>
                    <Condition Property="Text" Value="" />
                </MultiTrigger.Conditions>
            </MultiTrigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
</ResourceDictionary>


================================================
FILE: SimConnect/DEFINITION.cs
================================================
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global

namespace fs2ff.SimConnect
{
    public enum DEFINITION : uint
    {
        Undefined,
        Position,
        Attitude,
        Traffic
    }
}


================================================
FILE: SimConnect/EVENT.cs
================================================
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global

namespace fs2ff.SimConnect
{
    public enum EVENT : uint
    {
        Undefined,
        ObjectAdded,
        SixHz
    }
}


================================================
FILE: SimConnect/ISimConnectMessageHandler.cs
================================================
using System;

namespace fs2ff.SimConnect
{
    public interface ISimConnectMessageHandler : IDisposable
    {
        public void ReceiveFlightSimMessage();
        public IntPtr WindowHandle { set; }
    }
}


================================================
FILE: SimConnect/REQUEST.cs
================================================
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global

namespace fs2ff.SimConnect
{
    public enum REQUEST : uint
    {
        Undefined,
        Position,
        Attitude,
        TrafficAircraft,
        TrafficHelicopter,
        TrafficObjectBase = 0x01000000
    }
}


================================================
FILE: SimConnect/SimConnectAdapter.cs
================================================
// ReSharper disable InconsistentNaming

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using fs2ff.Models;
using Microsoft.FlightSimulator.SimConnect;
using SimConnectImpl = Microsoft.FlightSimulator.SimConnect.SimConnect;

namespace fs2ff.SimConnect
{
    public class SimConnectAdapter : IDisposable
    {
        private const string AppName = "fs2ff";
        private const uint WM_USER_SIMCONNECT = 0x0402;

        private Timer? _attitudeTimer;
        private SimConnectImpl? _simConnect;

        public event Func<Attitude, Task>? AttitudeReceived;
        public event Func<Position, Task>? PositionReceived;
        public event Action<bool>? StateChanged;
        public event Func<Traffic, uint, Task>? TrafficReceived;

        public bool Connected => _simConnect != null;

        public void Connect(IntPtr hwnd, uint attitudeFrequency)
        {
            try
            {
                UnsubscribeEvents();

                _simConnect?.Dispose();
                _attitudeTimer?.Dispose();

                _simConnect = new SimConnectImpl(AppName, hwnd, WM_USER_SIMCONNECT, null, 0);
                _attitudeTimer = new Timer(RequestAttitudeData, null, 100, 1000 / attitudeFrequency);

                SubscribeEvents();

                StateChanged?.Invoke(false);
            }
            catch (COMException e)
            {
                Console.Error.WriteLine("Exception caught: " + e);
                StateChanged?.Invoke(true);
            }
        }

        public void Disconnect() => DisconnectInternal(false);

        public void Dispose() => DisconnectInternal(false);

        public void ReceiveMessage()
        {
            try
            {
                _simConnect?.ReceiveMessage();
            }
            catch (COMException e)
            {
                Console.Error.WriteLine("Exception caught: " + e);
                DisconnectInternal(true);
            }
        }

        public void SetAttitudeFrequency(uint frequency)
        {
            _attitudeTimer?.Change(0, 1000 / frequency);
        }

        private void AddToDataDefinition(DEFINITION defineId, string datumName, string? unitsName, SIMCONNECT_DATATYPE datumType = SIMCONNECT_DATATYPE.FLOAT64)
        {
            _simConnect?.AddToDataDefinition(defineId, datumName, unitsName, datumType, 0, SimConnectImpl.SIMCONNECT_UNUSED);
        }

        private void DisconnectInternal(bool failure)
        {
            UnsubscribeEvents();

            _attitudeTimer?.Dispose();
            _attitudeTimer = null;

            _simConnect?.Dispose();
            _simConnect = null;

            StateChanged?.Invoke(failure);
        }

        private void RegisterAttitudeStruct()
        {
            AddToDataDefinition(DEFINITION.Attitude, "PLANE PITCH DEGREES", "Degrees");
            AddToDataDefinition(DEFINITION.Attitude, "PLANE BANK DEGREES", "Degrees");
            AddToDataDefinition(DEFINITION.Attitude, "PLANE HEADING DEGREES TRUE", "Degrees");

            _simConnect?.RegisterDataDefineStruct<Attitude>(DEFINITION.Attitude);
        }

        private void RegisterPositionStruct()
        {
            AddToDataDefinition(DEFINITION.Position, "PLANE LATITUDE", "Degrees");
            AddToDataDefinition(DEFINITION.Position, "PLANE LONGITUDE", "Degrees");
            AddToDataDefinition(DEFINITION.Position, "PLANE ALTITUDE", "Meters");
            AddToDataDefinition(DEFINITION.Position, "GPS GROUND TRUE TRACK", "Degrees");
            AddToDataDefinition(DEFINITION.Position, "GPS GROUND SPEED", "Meters per second");

            _simConnect?.RegisterDataDefineStruct<Position>(DEFINITION.Position);
        }

        private void RegisterTrafficStruct()
        {
            AddToDataDefinition(DEFINITION.Traffic, "PLANE LATITUDE", "Degrees");
            AddToDataDefinition(DEFINITION.Traffic, "PLANE LONGITUDE", "Degrees");
            AddToDataDefinition(DEFINITION.Traffic, "PLANE ALTITUDE", "Feet");
            AddToDataDefinition(DEFINITION.Traffic, "VELOCITY WORLD Y", "Feet per minute");
            AddToDataDefinition(DEFINITION.Traffic, "SIM ON GROUND", "Bool", SIMCONNECT_DATATYPE.INT32);
            AddToDataDefinition(DEFINITION.Traffic, "PLANE HEADING DEGREES TRUE", "Degrees");
            AddToDataDefinition(DEFINITION.Traffic, "GROUND VELOCITY", "Knots");
            AddToDataDefinition(DEFINITION.Traffic, "ATC ID", null, SIMCONNECT_DATATYPE.STRING64);
            AddToDataDefinition(DEFINITION.Traffic, "ATC AIRLINE", null, SIMCONNECT_DATATYPE.STRING64);
            AddToDataDefinition(DEFINITION.Traffic, "ATC FLIGHT NUMBER", null, SIMCONNECT_DATATYPE.STRING8);

            _simConnect?.RegisterDataDefineStruct<Traffic>(DEFINITION.Traffic);
        }

        private void RequestAttitudeData(object? _)
        {
            try
            {
                _simConnect?.RequestDataOnSimObject(
                    REQUEST.Attitude, DEFINITION.Attitude,
                    SimConnectImpl.SIMCONNECT_OBJECT_ID_USER,
                    SIMCONNECT_PERIOD.ONCE,
                    SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT,
                    0, 0, 0);
            }
            catch (COMException e)
            {
                Console.Error.WriteLine("Exception caught: " + e);
            }
        }

        private void SimConnect_OnRecvEventObjectAddremove(SimConnectImpl sender, SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE data)
        {
            if (data.uEventID == (uint) EVENT.ObjectAdded &&
                (data.eObjType == SIMCONNECT_SIMOBJECT_TYPE.AIRCRAFT ||
                 data.eObjType == SIMCONNECT_SIMOBJECT_TYPE.HELICOPTER) &&
                data.dwData != SimConnectImpl.SIMCONNECT_OBJECT_ID_USER)
            {
                _simConnect?.RequestDataOnSimObject(
                    REQUEST.TrafficObjectBase + data.dwData,
                    DEFINITION.Traffic, data.dwData,
                    SIMCONNECT_PERIOD.SECOND,
                    SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT,
                    0, 0, 0);
            }
        }

        private void SimConnect_OnRecvException(SimConnectImpl sender, SIMCONNECT_RECV_EXCEPTION data)
        {
            Console.Error.WriteLine("Exception caught: " + data.dwException);
            DisconnectInternal(true);
        }

        private void SimConnect_OnRecvOpen(SimConnectImpl sender, SIMCONNECT_RECV data)
        {
            RegisterPositionStruct();
            RegisterAttitudeStruct();
            RegisterTrafficStruct();

            _simConnect?.RequestDataOnSimObject(
                REQUEST.Position, DEFINITION.Position,
                SimConnectImpl.SIMCONNECT_OBJECT_ID_USER,
                SIMCONNECT_PERIOD.SECOND,
                SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT,
                0, 0, 0);

            _simConnect?.RequestDataOnSimObjectType(REQUEST.TrafficAircraft, DEFINITION.Traffic, 200000, SIMCONNECT_SIMOBJECT_TYPE.AIRCRAFT);
            _simConnect?.RequestDataOnSimObjectType(REQUEST.TrafficHelicopter, DEFINITION.Traffic, 200000, SIMCONNECT_SIMOBJECT_TYPE.HELICOPTER);

            _simConnect?.SubscribeToSystemEvent(EVENT.ObjectAdded, "ObjectAdded");
            _simConnect?.SubscribeToSystemEvent(EVENT.SixHz, "6Hz");
        }

        private void SimConnect_OnRecvQuit(SimConnectImpl sender, SIMCONNECT_RECV data)
        {
            DisconnectInternal(false);
        }

        private async void SimConnect_OnRecvSimobjectData(SimConnectImpl sender, SIMCONNECT_RECV_SIMOBJECT_DATA data)
        {
            if (data.dwRequestID == (uint) REQUEST.Position &&
                data.dwDefineID == (uint) DEFINITION.Position &&
                data.dwData?.FirstOrDefault() is Position pos)
            {
                await PositionReceived.RaiseAsync(pos).ConfigureAwait(false);
            }

            if (data.dwRequestID == (uint) REQUEST.Attitude &&
                data.dwDefineID == (uint) DEFINITION.Attitude &&
                data.dwData?.FirstOrDefault() is Attitude att)
            {
                await AttitudeReceived.RaiseAsync(att).ConfigureAwait(false);
            }

            if (data.dwRequestID == (uint) REQUEST.TrafficObjectBase + data.dwObjectID &&
                data.dwDefineID == (uint) DEFINITION.Traffic &&
                data.dwObjectID != SimConnectImpl.SIMCONNECT_OBJECT_ID_USER &&
                data.dwData?.FirstOrDefault() is Traffic tfk)
            {
                await TrafficReceived.RaiseAsync(tfk, data.dwObjectID).ConfigureAwait(false);
            }
        }

        private void SimConnect_OnRecvSimobjectDataBytype(SimConnectImpl sender, SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data)
        {
            if ((data.dwRequestID == (uint) REQUEST.TrafficAircraft ||
                 data.dwRequestID == (uint) REQUEST.TrafficHelicopter) &&
                data.dwDefineID == (uint) DEFINITION.Traffic &&
                data.dwObjectID != SimConnectImpl.SIMCONNECT_OBJECT_ID_USER)
            {
                _simConnect?.RequestDataOnSimObject(
                    REQUEST.TrafficObjectBase + data.dwObjectID,
                    DEFINITION.Traffic, data.dwObjectID,
                    SIMCONNECT_PERIOD.SECOND,
                    SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT,
                    0, 0, 0);
            }
        }

        private void SubscribeEvents()
        {
            if (_simConnect != null)
            {
                _simConnect.OnRecvOpen += SimConnect_OnRecvOpen;
                _simConnect.OnRecvQuit += SimConnect_OnRecvQuit;
                _simConnect.OnRecvException += SimConnect_OnRecvException;
                _simConnect.OnRecvSimobjectData += SimConnect_OnRecvSimobjectData;
                _simConnect.OnRecvSimobjectDataBytype += SimConnect_OnRecvSimobjectDataBytype;
                _simConnect.OnRecvEventObjectAddremove += SimConnect_OnRecvEventObjectAddremove;
            }
        }

        private void UnsubscribeEvents()
        {
            if (_simConnect != null)
            {
                _simConnect.OnRecvEventObjectAddremove -= SimConnect_OnRecvEventObjectAddremove;
                _simConnect.OnRecvSimobjectDataBytype -= SimConnect_OnRecvSimobjectDataBytype;
                _simConnect.OnRecvSimobjectData -= SimConnect_OnRecvSimobjectData;
                _simConnect.OnRecvException -= SimConnect_OnRecvException;
                _simConnect.OnRecvQuit -= SimConnect_OnRecvQuit;
                _simConnect.OnRecvOpen -= SimConnect_OnRecvOpen;
            }
        }
    }
}


================================================
FILE: UpdateChecker.cs
================================================
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace fs2ff
{
    public static class UpdateChecker
    {
        public static async Task<UpdateInformation?> Check()
        {
            try
            {
                using var handler = new HttpClientHandler { AllowAutoRedirect = false };
                using var client = new HttpClient(handler);

                var response = await client.GetAsync(new Uri("https://github.com/astenlund/fs2ff/releases/latest")).ConfigureAwait(false);
                var location = response.Headers.Location;
                var versionStr = location?.Segments[^1];

                return (Version.TryParse(versionStr?.TrimStart('v'), out var version) && version > App.AssemblyVersion)
                    ? new UpdateInformation(version, location!)
                    : null;
            }
            catch (Exception e)
            {
                await Console.Error.WriteLineAsync("Exception caught: " + e);
                return default;
            }
        }
    }
}


================================================
FILE: UpdateInformation.cs
================================================
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global

using System;

namespace fs2ff
{
    public class UpdateInformation
    {
        public UpdateInformation(Version version, Uri downloadLink)
        {
            Version = version;
            DownloadLink = downloadLink;
        }

        public Version Version { get; }

        public Uri DownloadLink { get; }
    }
}


================================================
FILE: ViewModelLocator.cs
================================================
namespace fs2ff
{
    public class ViewModelLocator
    {
        public static MainViewModel Main => App.GetRequiredService<MainViewModel>();
    }
}


================================================
FILE: fs2ff.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <ApplicationIcon>img\icon.ico</ApplicationIcon>
    <UseWPF>true</UseWPF>
    <LangVersion>8</LangVersion>
    <Nullable>enable</Nullable>
    <Version>0.0.0-dev</Version>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="Microsoft.FlightSimulator.SimConnect, Version=11.0.62651.3, Culture=neutral, PublicKeyToken=baf445ffb3a06b5c">
      <HintPath>lib\Microsoft.FlightSimulator.SimConnect.dll</HintPath>
    </Reference>
  </ItemGroup>

  <ItemGroup>
    <ContentWithTargetPath Include="lib\concrt140.dll">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <TargetPath>concrt140.dll</TargetPath>
    </ContentWithTargetPath>
    <ContentWithTargetPath Include="lib\msvcp140.dll">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <TargetPath>msvcp140.dll</TargetPath>
    </ContentWithTargetPath>
    <ContentWithTargetPath Include="lib\SimConnect.dll">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <TargetPath>SimConnect.dll</TargetPath>
    </ContentWithTargetPath>
    <ContentWithTargetPath Include="lib\vcruntime140.dll">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <TargetPath>vcruntime140.dll</TargetPath>
    </ContentWithTargetPath>
    <ContentWithTargetPath Include="lib\vcruntime140_1.dll">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <TargetPath>vcruntime140_1.dll</TargetPath>
    </ContentWithTargetPath>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Fody" Version="6.6.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.37" />
    <PackageReference Include="ModernWpfUI" Version="0.9.4" />
    <PackageReference Include="PropertyChanged.Fody" Version="3.4.0">
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <None Remove="img\icon.ico" />
    <Resource Include="img\icon.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
  </ItemGroup>

  <ItemGroup>
    <Compile Update="Preferences.Designer.cs">
      <DesignTimeSharedInput>True</DesignTimeSharedInput>
      <AutoGen>True</AutoGen>
      <DependentUpon>Preferences.settings</DependentUpon>
    </Compile>
  </ItemGroup>

  <ItemGroup>
    <None Update="Preferences.settings">
      <Generator>SettingsSingleFileGenerator</Generator>
      <LastGenOutput>Preferences.Designer.cs</LastGenOutput>
    </None>
  </ItemGroup>

</Project>


================================================
FILE: fs2ff.sln
================================================

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fs2ff", "fs2ff.csproj", "{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}"
EndProject
Global
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Debug|x64 = Debug|x64
		Debug|x86 = Debug|x86
		Release|Any CPU = Release|Any CPU
		Release|x64 = Release|x64
		Release|x86 = Release|x86
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Debug|x64.ActiveCfg = Debug|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Debug|x64.Build.0 = Debug|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Debug|x86.ActiveCfg = Debug|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Debug|x86.Build.0 = Debug|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Release|Any CPU.Build.0 = Release|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Release|x64.ActiveCfg = Release|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Release|x64.Build.0 = Release|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Release|x86.ActiveCfg = Release|Any CPU
		{95A935C1-D0D3-43E6-BA38-CB19BD196F5C}.Release|x86.Build.0 = Release|Any CPU
	EndGlobalSection
EndGlobal


================================================
FILE: global.json
================================================
{
  "sdk": {
    "version": "3.1.100",
    "rollForward": "latestFeature"
  }
}


================================================
FILE: publish.ps1
================================================
param (
    [ValidateSet('Debug','Release')]
    [string]$Configuration = 'Release',
    [ValidateSet('win-x64','win10-x64')]
    [string]$Runtime = 'win-x64',
    [Parameter(Mandatory)]
    [string]$Version,
    [ValidateSet('quiet','minimal','normal','detailed','diagnostic')]
    [string]$Verbosity = "minimal",
    [switch]$UseR2R
)

$PublishDir = "publish"

if (Test-Path $PublishDir) { Remove-Item $PublishDir -Recurse -Verbose }

dotnet restore

dotnet publish `
    -o $PublishDir `
    -r $Runtime `
    -c $Configuration `
    -v $Verbosity `
    /p:DebugType=none `
    /p:DebugSymbols=false `
    /p:SelfContained=true `
    /p:PublishSingleFile=true `
    /P:PublishReadyToRun=$UseR2R `
    /p:PublishReadyToRunShowWarnings=$UseR2R `
    /p:Version=$Version

Get-Item "$PublishDir\fs2ff.exe" |
    Select-Object Name,
        @{ Name = 'Version'; Expression = { $_.VersionInfo.ProductVersion }},
        @{ Name = 'Size'; Expression = { "{0:f0} MB`r`n" -f ($_.Length / 1MB) }} |
    Format-List
Download .txt
gitextract_pdi8_1c4/

├── .editorconfig
├── .github/
│   └── dependabot.yml
├── .gitignore
├── .vscode/
│   ├── launch.json
│   └── tasks.json
├── ActionCommand.cs
├── Annotations.cs
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── Behaviors/
│   ├── MoveFocusOnEnterBehavior.cs
│   └── UpdateSourceOnLostFocusBehavior.cs
├── Converters/
│   ├── BooleanToDoubleConverter.cs
│   ├── BooleanToVisibilityConverter.cs
│   ├── FormatStringConverter.cs
│   ├── InvertedBooleanConverter.cs
│   ├── IpAddressToStringConverter.cs
│   └── UIntToDoubleConverter.cs
├── DataSender.cs
├── Extensions.cs
├── FodyWeavers.xml
├── FodyWeavers.xsd
├── IpDetectionService.cs
├── LICENSE
├── MainViewModel.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Models/
│   ├── Attitude.cs
│   ├── Position.cs
│   └── Traffic.cs
├── Preferences.Designer.cs
├── Preferences.settings
├── README.md
├── Resources.xaml
├── SimConnect/
│   ├── DEFINITION.cs
│   ├── EVENT.cs
│   ├── ISimConnectMessageHandler.cs
│   ├── REQUEST.cs
│   └── SimConnectAdapter.cs
├── UpdateChecker.cs
├── UpdateInformation.cs
├── ViewModelLocator.cs
├── fs2ff.csproj
├── fs2ff.sln
├── global.json
└── publish.ps1
Download .txt
SYMBOL INDEX (261 symbols across 28 files)

FILE: ActionCommand.cs
  class ActionCommand (line 9) | public class ActionCommand : ICommand
    method ActionCommand (line 14) | public ActionCommand()
    method ActionCommand (line 20) | public ActionCommand(Action action)
    method ActionCommand (line 26) | public ActionCommand(Action action, Func<bool> predicate)
    method CanExecute (line 34) | public bool CanExecute(object? _) => _predicate();
    method Execute (line 36) | public void Execute(object? parameter) => _action();
    method TriggerCanExecuteChanged (line 38) | public void TriggerCanExecuteChanged() => CanExecuteChanged?.Invoke(th...
    method ActionCommand (line 46) | public ActionCommand()
    method ActionCommand (line 52) | public ActionCommand(Action<T?> action)
    method ActionCommand (line 58) | public ActionCommand(Action<T?> action, Func<T?, bool> predicate)
    method CanExecute (line 66) | public bool CanExecute(object? parameter) => parameter is T param ? _p...
    method Execute (line 68) | public void Execute(object? parameter)
    method TriggerCanExecuteChanged (line 74) | public void TriggerCanExecuteChanged() => CanExecuteChanged?.Invoke(th...
  class ActionCommand (line 41) | public class ActionCommand<T> : ICommand where T : struct
    method ActionCommand (line 14) | public ActionCommand()
    method ActionCommand (line 20) | public ActionCommand(Action action)
    method ActionCommand (line 26) | public ActionCommand(Action action, Func<bool> predicate)
    method CanExecute (line 34) | public bool CanExecute(object? _) => _predicate();
    method Execute (line 36) | public void Execute(object? parameter) => _action();
    method TriggerCanExecuteChanged (line 38) | public void TriggerCanExecuteChanged() => CanExecuteChanged?.Invoke(th...
    method ActionCommand (line 46) | public ActionCommand()
    method ActionCommand (line 52) | public ActionCommand(Action<T?> action)
    method ActionCommand (line 58) | public ActionCommand(Action<T?> action, Func<T?, bool> predicate)
    method CanExecute (line 66) | public bool CanExecute(object? parameter) => parameter is T param ? _p...
    method Execute (line 68) | public void Execute(object? parameter)
    method TriggerCanExecuteChanged (line 74) | public void TriggerCanExecuteChanged() => CanExecuteChanged?.Invoke(th...

FILE: Annotations.cs
  class CanBeNullAttribute (line 48) | [AttributeUsage(
  class NotNullAttribute (line 62) | [AttributeUsage(
  class ItemNotNullAttribute (line 82) | [AttributeUsage(
  class ItemCanBeNullAttribute (line 102) | [AttributeUsage(
  class StringFormatMethodAttribute (line 120) | [AttributeUsage(
    method StringFormatMethodAttribute (line 128) | public StringFormatMethodAttribute([NotNull] string formatParameterName)
  class ValueProviderAttribute (line 163) | [AttributeUsage(
    method ValueProviderAttribute (line 168) | public ValueProviderAttribute([NotNull] string name)
  class ValueRangeAttribute (line 188) | [AttributeUsage(
    method ValueRangeAttribute (line 197) | public ValueRangeAttribute(long from, long to)
    method ValueRangeAttribute (line 203) | public ValueRangeAttribute(ulong from, ulong to)
    method ValueRangeAttribute (line 209) | public ValueRangeAttribute(long value)
    method ValueRangeAttribute (line 214) | public ValueRangeAttribute(ulong value)
  class NonNegativeValueAttribute (line 230) | [AttributeUsage(
  class InvokerParameterNameAttribute (line 246) | [AttributeUsage(AttributeTargets.Parameter)]
  class NotifyPropertyChangedInvocatorAttribute (line 287) | [AttributeUsage(AttributeTargets.Method)]
    method NotifyPropertyChangedInvocatorAttribute (line 290) | public NotifyPropertyChangedInvocatorAttribute() { }
    method NotifyPropertyChangedInvocatorAttribute (line 291) | public NotifyPropertyChangedInvocatorAttribute([NotNull] string parame...
  class ContractAnnotationAttribute (line 343) | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    method ContractAnnotationAttribute (line 346) | public ContractAnnotationAttribute([NotNull] string contract)
    method ContractAnnotationAttribute (line 349) | public ContractAnnotationAttribute([NotNull] string contract, bool for...
  class LocalizationRequiredAttribute (line 369) | [AttributeUsage(AttributeTargets.All)]
    method LocalizationRequiredAttribute (line 372) | public LocalizationRequiredAttribute() : this(true) { }
    method LocalizationRequiredAttribute (line 374) | public LocalizationRequiredAttribute(bool required)
  class CannotApplyEqualityOperatorAttribute (line 402) | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | At...
  class BaseTypeRequiredAttribute (line 416) | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    method BaseTypeRequiredAttribute (line 420) | public BaseTypeRequiredAttribute([NotNull] Type baseType)
  class UsedImplicitlyAttribute (line 432) | [AttributeUsage(AttributeTargets.All)]
    method UsedImplicitlyAttribute (line 435) | public UsedImplicitlyAttribute()
    method UsedImplicitlyAttribute (line 438) | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)
    method UsedImplicitlyAttribute (line 441) | public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)
    method UsedImplicitlyAttribute (line 444) | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, Impl...
  class MeansImplicitUseAttribute (line 461) | [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParamet...
    method MeansImplicitUseAttribute (line 464) | public MeansImplicitUseAttribute()
    method MeansImplicitUseAttribute (line 467) | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags)
    method MeansImplicitUseAttribute (line 470) | public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags)
    method MeansImplicitUseAttribute (line 473) | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, Im...
  type ImplicitUseKindFlags (line 488) | [Flags]
  type ImplicitUseTargetFlags (line 509) | [Flags]
  class PublicAPIAttribute (line 526) | [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
    method PublicAPIAttribute (line 530) | public PublicAPIAttribute() { }
    method PublicAPIAttribute (line 532) | public PublicAPIAttribute([NotNull] string comment)
  class InstantHandleAttribute (line 545) | [AttributeUsage(AttributeTargets.Parameter)]
  class PureAttribute (line 559) | [AttributeUsage(AttributeTargets.Method)]
  class MustUseReturnValueAttribute (line 573) | [AttributeUsage(AttributeTargets.Method)]
    method MustUseReturnValueAttribute (line 576) | public MustUseReturnValueAttribute() { }
    method MustUseReturnValueAttribute (line 578) | public MustUseReturnValueAttribute([NotNull] string justification)
  class ProvidesContextAttribute (line 601) | [AttributeUsage(
  class PathReferenceAttribute (line 610) | [AttributeUsage(AttributeTargets.Parameter)]
    method PathReferenceAttribute (line 613) | public PathReferenceAttribute() { }
    method PathReferenceAttribute (line 615) | public PathReferenceAttribute([NotNull, PathReference] string basePath)
  class SourceTemplateAttribute (line 646) | [AttributeUsage(AttributeTargets.Method)]
  class MacroAttribute (line 677) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, Al...
  class AspMvcAreaMasterLocationFormatAttribute (line 703) | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | Att...
    method AspMvcAreaMasterLocationFormatAttribute (line 706) | public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format)
  class AspMvcAreaPartialViewLocationFormatAttribute (line 714) | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | Att...
    method AspMvcAreaPartialViewLocationFormatAttribute (line 717) | public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string f...
  class AspMvcAreaViewLocationFormatAttribute (line 725) | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | Att...
    method AspMvcAreaViewLocationFormatAttribute (line 728) | public AspMvcAreaViewLocationFormatAttribute([NotNull] string format)
  class AspMvcMasterLocationFormatAttribute (line 736) | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | Att...
    method AspMvcMasterLocationFormatAttribute (line 739) | public AspMvcMasterLocationFormatAttribute([NotNull] string format)
  class AspMvcPartialViewLocationFormatAttribute (line 747) | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | Att...
    method AspMvcPartialViewLocationFormatAttribute (line 750) | public AspMvcPartialViewLocationFormatAttribute([NotNull] string format)
  class AspMvcViewLocationFormatAttribute (line 758) | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | Att...
    method AspMvcViewLocationFormatAttribute (line 761) | public AspMvcViewLocationFormatAttribute([NotNull] string format)
  class AspMvcActionAttribute (line 775) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | A...
    method AspMvcActionAttribute (line 778) | public AspMvcActionAttribute() { }
    method AspMvcActionAttribute (line 780) | public AspMvcActionAttribute([NotNull] string anonymousProperty)
  class AspMvcAreaAttribute (line 793) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | At...
    method AspMvcAreaAttribute (line 796) | public AspMvcAreaAttribute() { }
    method AspMvcAreaAttribute (line 798) | public AspMvcAreaAttribute([NotNull] string anonymousProperty)
  class AspMvcControllerAttribute (line 812) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | A...
    method AspMvcControllerAttribute (line 815) | public AspMvcControllerAttribute() { }
    method AspMvcControllerAttribute (line 817) | public AspMvcControllerAttribute([NotNull] string anonymousProperty)
  class AspMvcMasterAttribute (line 829) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | At...
  class AspMvcModelTypeAttribute (line 836) | [AttributeUsage(AttributeTargets.Parameter)]
  class AspMvcPartialViewAttribute (line 845) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | A...
  class AspMvcSuppressViewErrorAttribute (line 851) | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  class AspMvcDisplayTemplateAttribute (line 859) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | At...
  class AspMvcEditorTemplateAttribute (line 867) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | At...
  class AspMvcTemplateAttribute (line 875) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | At...
  class AspMvcViewAttribute (line 884) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | A...
  class AspMvcViewComponentAttribute (line 891) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | At...
  class AspMvcViewComponentViewAttribute (line 898) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | A...
  class AspMvcActionSelectorAttribute (line 912) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
  class HtmlElementAttributesAttribute (line 915) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property |...
    method HtmlElementAttributesAttribute (line 918) | public HtmlElementAttributesAttribute() { }
    method HtmlElementAttributesAttribute (line 920) | public HtmlElementAttributesAttribute([NotNull] string name)
  class HtmlAttributeValueAttribute (line 928) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | At...
    method HtmlAttributeValueAttribute (line 931) | public HtmlAttributeValueAttribute([NotNull] string name)
  class RazorSectionAttribute (line 944) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
  class CollectionAccessAttribute (line 974) | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor |...
    method CollectionAccessAttribute (line 977) | public CollectionAccessAttribute(CollectionAccessType collectionAccess...
  type CollectionAccessType (line 989) | [Flags]
  class AssertionMethodAttribute (line 1007) | [AttributeUsage(AttributeTargets.Method)]
  class AssertionConditionAttribute (line 1015) | [AttributeUsage(AttributeTargets.Parameter)]
    method AssertionConditionAttribute (line 1018) | public AssertionConditionAttribute(AssertionConditionType conditionType)
  type AssertionConditionType (line 1030) | public enum AssertionConditionType
  class TerminatesProgramAttribute (line 1046) | [Obsolete("Use [ContractAnnotation('=> halt')] instead")]
  class LinqTunnelAttribute (line 1055) | [AttributeUsage(AttributeTargets.Method)]
  class NoEnumerationAttribute (line 1074) | [AttributeUsage(AttributeTargets.Parameter)]
  class RegexPatternAttribute (line 1080) | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | At...
  class NoReorderAttribute (line 1089) | [AttributeUsage(
  class XamlItemsControlAttribute (line 1097) | [AttributeUsage(AttributeTargets.Class)]
  class XamlItemBindingOfItemsControlAttribute (line 1109) | [AttributeUsage(AttributeTargets.Property)]
  class XamlItemStyleOfItemsControlAttribute (line 1121) | [AttributeUsage(AttributeTargets.Property)]
  class AspChildControlTypeAttribute (line 1124) | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    method AspChildControlTypeAttribute (line 1127) | public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull...
  class AspDataFieldAttribute (line 1138) | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
  class AspDataFieldsAttribute (line 1141) | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
  class AspMethodPropertyAttribute (line 1144) | [AttributeUsage(AttributeTargets.Property)]
  class AspRequiredAttributeAttribute (line 1147) | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    method AspRequiredAttributeAttribute (line 1150) | public AspRequiredAttributeAttribute([NotNull] string attribute)
  class AspTypePropertyAttribute (line 1158) | [AttributeUsage(AttributeTargets.Property)]
    method AspTypePropertyAttribute (line 1163) | public AspTypePropertyAttribute(bool createConstructorReferences)
  class RazorImportNamespaceAttribute (line 1169) | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
    method RazorImportNamespaceAttribute (line 1172) | public RazorImportNamespaceAttribute([NotNull] string name)
  class RazorInjectionAttribute (line 1180) | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
    method RazorInjectionAttribute (line 1183) | public RazorInjectionAttribute([NotNull] string type, [NotNull] string...
  class RazorDirectiveAttribute (line 1194) | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
    method RazorDirectiveAttribute (line 1197) | public RazorDirectiveAttribute([NotNull] string directive)
  class RazorPageBaseTypeAttribute (line 1205) | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
    method RazorPageBaseTypeAttribute (line 1208) | public RazorPageBaseTypeAttribute([NotNull] string baseType)
    method RazorPageBaseTypeAttribute (line 1212) | public RazorPageBaseTypeAttribute([NotNull] string baseType, string pa...
  class RazorHelperCommonAttribute (line 1222) | [AttributeUsage(AttributeTargets.Method)]
  class RazorLayoutAttribute (line 1225) | [AttributeUsage(AttributeTargets.Property)]
  class RazorWriteLiteralMethodAttribute (line 1228) | [AttributeUsage(AttributeTargets.Method)]
  class RazorWriteMethodAttribute (line 1231) | [AttributeUsage(AttributeTargets.Method)]
  class RazorWriteMethodParameterAttribute (line 1234) | [AttributeUsage(AttributeTargets.Parameter)]

FILE: App.xaml.cs
  class App (line 13) | public partial class App
    method OnExit (line 19) | protected override async void OnExit(ExitEventArgs e)
    method OnStartup (line 26) | protected override async void OnStartup(StartupEventArgs e)
    method GetRequiredService (line 39) | public static T GetRequiredService<T>() where T : class => Host.Servic...
    method ConfigureServices (line 41) | public static void ConfigureServices(IServiceCollection services)

FILE: Behaviors/MoveFocusOnEnterBehavior.cs
  class MoveFocusOnEnterBehavior (line 7) | public class MoveFocusOnEnterBehavior : Behavior<UIElement>
    method OnAttached (line 9) | protected override void OnAttached()
    method OnDetaching (line 17) | protected override void OnDetaching()
    method AssociatedObject_KeyDown (line 25) | private static void AssociatedObject_KeyDown(object sender, KeyEventAr...

FILE: Behaviors/UpdateSourceOnLostFocusBehavior.cs
  class UpdateSourceOnLostFocusBehavior (line 8) | public class UpdateSourceOnLostFocusBehavior : Behavior<TextBox>
    method OnAttached (line 10) | protected override void OnAttached()
    method OnDetaching (line 18) | protected override void OnDetaching()
    method AssociatedObject_LostFocus (line 26) | private static void AssociatedObject_LostFocus(object sender, RoutedEv...

FILE: Converters/BooleanToDoubleConverter.cs
  class BooleanToDoubleConverter (line 9) | public class BooleanToDoubleConverter : MarkupExtension, IValueConverter
    method Convert (line 11) | public object Convert(object value, Type targetType, object parameter,...
    method ConvertBack (line 26) | public object ConvertBack(object value, Type targetType, object parame...
    method ProvideValue (line 31) | public override object ProvideValue(IServiceProvider serviceProvider)

FILE: Converters/BooleanToVisibilityConverter.cs
  class BooleanToVisibilityConverter (line 9) | public class BooleanToVisibilityConverter : MarkupExtension, IValueConve...
    method Convert (line 11) | public object Convert(object value, Type targetType, object parameter,...
    method ConvertBack (line 20) | public object ConvertBack(object value, Type targetType, object parame...
    method ProvideValue (line 27) | public override object ProvideValue(IServiceProvider serviceProvider)

FILE: Converters/FormatStringConverter.cs
  class FormatStringConverter (line 8) | public class FormatStringConverter : MarkupExtension, IValueConverter
    method Convert (line 10) | public object Convert(object value, Type targetType, object? parameter...
    method ConvertBack (line 17) | public object ConvertBack(object value, Type targetType, object parame...
    method ProvideValue (line 22) | public override object ProvideValue(IServiceProvider serviceProvider)

FILE: Converters/InvertedBooleanConverter.cs
  class InvertedBooleanConverter (line 8) | public class InvertedBooleanConverter : MarkupExtension, IValueConverter
    method Convert (line 10) | public object Convert(object value, Type targetType, object parameter,...
    method ConvertBack (line 15) | public object ConvertBack(object value, Type targetType, object parame...
    method ProvideValue (line 20) | public override object ProvideValue(IServiceProvider serviceProvider)

FILE: Converters/IpAddressToStringConverter.cs
  class IpAddressToStringConverter (line 10) | public class IpAddressToStringConverter : MarkupExtension, IValueConverter
    method Convert (line 14) | public object Convert(object? value, Type targetType, object parameter...
    method ConvertBack (line 24) | public object? ConvertBack(object? value, Type targetType, object para...
    method ProvideValue (line 43) | public override object ProvideValue(IServiceProvider serviceProvider)

FILE: Converters/UIntToDoubleConverter.cs
  class UIntToDoubleConverter (line 8) | public class UIntToDoubleConverter : MarkupExtension, IValueConverter
    method Convert (line 10) | public object Convert(object value, Type targetType, object parameter,...
    method ConvertBack (line 17) | public object ConvertBack(object value, Type targetType, object parame...
    method ProvideValue (line 24) | public override object ProvideValue(IServiceProvider serviceProvider)

FILE: DataSender.cs
  class DataSender (line 11) | public class DataSender : IDisposable
    method Connect (line 19) | public void Connect(IPAddress? ip)
    method Disconnect (line 32) | public void Disconnect() => _socket?.Dispose();
    method Dispose (line 34) | public void Dispose() => _socket?.Dispose();
    method Send (line 36) | public async Task Send(Attitude a)
    method Send (line 45) | public async Task Send(Position p)
    method Send (line 54) | public async Task Send(Traffic t, uint id)
    method Send (line 64) | private async Task Send(string data)
    method TryGetFlightNumber (line 74) | private static string? TryGetFlightNumber(Traffic t) =>

FILE: Extensions.cs
  class Extensions (line 6) | public static class Extensions
    method AdjustToBounds (line 8) | public static T AdjustToBounds<T>(this T value, T min, T max) where T ...
    method RaiseAsync (line 15) | public static async Task RaiseAsync<T>(this Func<T, Task>? handler, T ...
    method RaiseAsync (line 33) | public static async Task RaiseAsync<T1, T2>(this Func<T1, T2, Task>? h...

FILE: IpDetectionService.cs
  class IpDetectionService (line 12) | public class IpDetectionService : IHostedService
    method StartAsync (line 18) | public async Task StartAsync(CancellationToken cancellationToken)
    method StopAsync (line 44) | public Task StopAsync(CancellationToken cancellationToken) => Task.Com...
    method IsForeFlightGdl90 (line 46) | private static bool IsForeFlightGdl90(string text)

FILE: MainViewModel.cs
  class MainViewModel (line 16) | [SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = ...
    method MainViewModel (line 36) | public MainViewModel(DataSender dataSender, SimConnectAdapter simConne...
    type FlightSimState (line 67) | [SuppressMessage("ReSharper", "UnusedMember.Local", Justification = "U...
    method Dispose (line 257) | public void Dispose()
    method ReceiveFlightSimMessage (line 270) | public void ReceiveFlightSimMessage() => _simConnect.ReceiveMessage();
    method AutoConnectCallback (line 272) | private void AutoConnectCallback(object? sender, EventArgs e) => Conne...
    method CanConnect (line 274) | private bool CanConnect() => WindowHandle != IntPtr.Zero;
    method CheckForUpdates (line 276) | private void CheckForUpdates()
    method Connect (line 281) | private void Connect() => _simConnect.Connect(WindowHandle, AttitudeFr...
    method Disconnect (line 283) | private void Disconnect() => _simConnect.Disconnect();
    method DismissSettingsPane (line 285) | private void DismissSettingsPane() => SettingsPaneVisible = false;
    method GotoReleaseNotesPage (line 288) | private void GotoReleaseNotesPage()
    method IpDetectionService_NewIpDetected (line 296) | private void IpDetectionService_NewIpDetected(IPAddress ip)
    method IpHintCallback (line 304) | private void IpHintCallback(object? sender, EventArgs e)
    method ManageAutoConnect (line 312) | private void ManageAutoConnect()
    method OpenSettings (line 324) | private void OpenSettings() => SettingsPaneVisible = true;
    method ResetDataSenderConnection (line 326) | private void ResetDataSenderConnection()
    method ResetIpHintMinutesLeft (line 338) | private void ResetIpHintMinutesLeft()
    method SimConnectAttitudeReceived (line 345) | private async Task SimConnectAttitudeReceived(Attitude att)
    method SimConnectPositionReceived (line 353) | private async Task SimConnectPositionReceived(Position pos)
    method SimConnectStateChanged (line 361) | private void SimConnectStateChanged(bool failure)
    method SimConnectTrafficReceived (line 370) | private async Task SimConnectTrafficReceived(Traffic tfk, uint id)
    method ToggleConnect (line 379) | private void ToggleConnect()
    method ToggleSettingsPane (line 385) | private void ToggleSettingsPane() => SettingsPaneVisible = !SettingsPa...
    method UpdateVisualState (line 387) | private void UpdateVisualState()

FILE: MainWindow.xaml.cs
  class MainWindow (line 11) | public partial class MainWindow
    method MainWindow (line 15) | public MainWindow()
    method Window_Closing (line 20) | private void Window_Closing(object sender, CancelEventArgs e)
    method Window_Loaded (line 25) | private void Window_Loaded(object sender, RoutedEventArgs e)
    method WndProc (line 32) | private IntPtr WndProc(IntPtr hWnd, int iMsg, IntPtr hWParam, IntPtr h...

FILE: Models/Attitude.cs
  type Attitude (line 7) | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]

FILE: Models/Position.cs
  type Position (line 7) | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]

FILE: Models/Traffic.cs
  type Traffic (line 7) | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]

FILE: Preferences.Designer.cs
  class Preferences (line 14) | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]

FILE: SimConnect/DEFINITION.cs
  type DEFINITION (line 6) | public enum DEFINITION : uint

FILE: SimConnect/EVENT.cs
  type EVENT (line 6) | public enum EVENT : uint

FILE: SimConnect/ISimConnectMessageHandler.cs
  type ISimConnectMessageHandler (line 5) | public interface ISimConnectMessageHandler : IDisposable
    method ReceiveFlightSimMessage (line 7) | public void ReceiveFlightSimMessage();

FILE: SimConnect/REQUEST.cs
  type REQUEST (line 6) | public enum REQUEST : uint

FILE: SimConnect/SimConnectAdapter.cs
  class SimConnectAdapter (line 14) | public class SimConnectAdapter : IDisposable
    method Connect (line 29) | public void Connect(IntPtr hwnd, uint attitudeFrequency)
    method Disconnect (line 52) | public void Disconnect() => DisconnectInternal(false);
    method Dispose (line 54) | public void Dispose() => DisconnectInternal(false);
    method ReceiveMessage (line 56) | public void ReceiveMessage()
    method SetAttitudeFrequency (line 69) | public void SetAttitudeFrequency(uint frequency)
    method AddToDataDefinition (line 74) | private void AddToDataDefinition(DEFINITION defineId, string datumName...
    method DisconnectInternal (line 79) | private void DisconnectInternal(bool failure)
    method RegisterAttitudeStruct (line 92) | private void RegisterAttitudeStruct()
    method RegisterPositionStruct (line 101) | private void RegisterPositionStruct()
    method RegisterTrafficStruct (line 112) | private void RegisterTrafficStruct()
    method RequestAttitudeData (line 128) | private void RequestAttitudeData(object? _)
    method SimConnect_OnRecvEventObjectAddremove (line 145) | private void SimConnect_OnRecvEventObjectAddremove(SimConnectImpl send...
    method SimConnect_OnRecvException (line 161) | private void SimConnect_OnRecvException(SimConnectImpl sender, SIMCONN...
    method SimConnect_OnRecvOpen (line 167) | private void SimConnect_OnRecvOpen(SimConnectImpl sender, SIMCONNECT_R...
    method SimConnect_OnRecvQuit (line 187) | private void SimConnect_OnRecvQuit(SimConnectImpl sender, SIMCONNECT_R...
    method SimConnect_OnRecvSimobjectData (line 192) | private async void SimConnect_OnRecvSimobjectData(SimConnectImpl sende...
    method SimConnect_OnRecvSimobjectDataBytype (line 217) | private void SimConnect_OnRecvSimobjectDataBytype(SimConnectImpl sende...
    method SubscribeEvents (line 233) | private void SubscribeEvents()
    method UnsubscribeEvents (line 246) | private void UnsubscribeEvents()

FILE: UpdateChecker.cs
  class UpdateChecker (line 7) | public static class UpdateChecker
    method Check (line 9) | public static async Task<UpdateInformation?> Check()

FILE: UpdateInformation.cs
  class UpdateInformation (line 8) | public class UpdateInformation
    method UpdateInformation (line 10) | public UpdateInformation(Version version, Uri downloadLink)

FILE: ViewModelLocator.cs
  class ViewModelLocator (line 3) | public class ViewModelLocator
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (157K chars).
[
  {
    "path": ".editorconfig",
    "chars": 6644,
    "preview": "# Remove the line below if you want to inherit .editorconfig settings from higher directories\nroot = true\n\n# ReSharper "
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 529,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".gitignore",
    "chars": 7172,
    "preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## "
  },
  {
    "path": ".vscode/launch.json",
    "chars": 941,
    "preview": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      // Use IntelliSense to find out which attributes exist for C# "
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 960,
    "preview": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"label\": \"build\",\n      \"command\": \"dotnet\",\n      \"type\": \"process\",\n "
  },
  {
    "path": "ActionCommand.cs",
    "chars": 2007,
    "preview": "// ReSharper disable UnusedMember.Global\n// ReSharper disable UnusedType.Global\n\nusing System;\nusing System.Windows.Inp"
  },
  {
    "path": "Annotations.cs",
    "chars": 47983,
    "preview": "/* MIT License\n\nCopyright (c) 2016 JetBrains http://www.jetbrains.com\n\nPermission is hereby granted, free of charge, to"
  },
  {
    "path": "App.xaml",
    "chars": 1636,
    "preview": "<Application x:Class=\"fs2ff.App\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n       "
  },
  {
    "path": "App.xaml.cs",
    "chars": 1639,
    "preview": "using System;\nusing System.Reflection;\nusing System.Windows;\nusing fs2ff.SimConnect;\nusing Microsoft.Extensions.Depende"
  },
  {
    "path": "AssemblyInfo.cs",
    "chars": 595,
    "preview": "using System.Windows;\n\n[assembly:ThemeInfo(\n    ResourceDictionaryLocation.None, //where theme specific resource dictio"
  },
  {
    "path": "Behaviors/MoveFocusOnEnterBehavior.cs",
    "chars": 871,
    "preview": "using System.Windows;\nusing System.Windows.Input;\nusing Microsoft.Xaml.Behaviors;\n\nnamespace fs2ff.Behaviors\n{\n    publi"
  },
  {
    "path": "Behaviors/UpdateSourceOnLostFocusBehavior.cs",
    "chars": 909,
    "preview": "using System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Data;\nusing Microsoft.Xaml.Behaviors;\n\nnamespa"
  },
  {
    "path": "Converters/BooleanToDoubleConverter.cs",
    "chars": 1145,
    "preview": "using System;\nusing System.Globalization;\nusing System.Linq;\nusing System.Windows.Data;\nusing System.Windows.Markup;\n\nna"
  },
  {
    "path": "Converters/BooleanToVisibilityConverter.cs",
    "chars": 943,
    "preview": "using System;\nusing System.Globalization;\nusing System.Windows;\nusing System.Windows.Data;\nusing System.Windows.Markup;\n"
  },
  {
    "path": "Converters/FormatStringConverter.cs",
    "chars": 753,
    "preview": "using System;\nusing System.Globalization;\nusing System.Windows.Data;\nusing System.Windows.Markup;\n\nnamespace fs2ff.Conve"
  },
  {
    "path": "Converters/InvertedBooleanConverter.cs",
    "chars": 654,
    "preview": "using System;\nusing System.Globalization;\nusing System.Windows.Data;\nusing System.Windows.Markup;\n\nnamespace fs2ff.Conve"
  },
  {
    "path": "Converters/IpAddressToStringConverter.cs",
    "chars": 1369,
    "preview": "using System;\nusing System.Globalization;\nusing System.Net;\nusing System.Text.RegularExpressions;\nusing System.Windows.D"
  },
  {
    "path": "Converters/UIntToDoubleConverter.cs",
    "chars": 796,
    "preview": "using System;\nusing System.Globalization;\nusing System.Windows.Data;\nusing System.Windows.Markup;\n\nnamespace fs2ff.Conve"
  },
  {
    "path": "DataSender.cs",
    "chars": 2614,
    "preview": "using System;\nusing System.Globalization;\nusing System.Net;\nusing System.Net.Sockets;\nusing System.Text;\nusing System.T"
  },
  {
    "path": "Extensions.cs",
    "chars": 1404,
    "preview": "using System;\nusing System.Threading.Tasks;\n\nnamespace fs2ff\n{\n    public static class Extensions\n    {\n        public "
  },
  {
    "path": "FodyWeavers.xml",
    "chars": 145,
    "preview": "<Weavers xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"FodyWeavers.xsd\">\n  <Prop"
  },
  {
    "path": "FodyWeavers.xsd",
    "chars": 4038,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n  <!-- This file was gen"
  },
  {
    "path": "IpDetectionService.cs",
    "chars": 1736,
    "preview": "using System;\nusing System.Net;\nusing System.Net.Sockets;\nusing System.Text;\nusing System.Text.Json;\nusing System.Thread"
  },
  {
    "path": "LICENSE",
    "chars": 1211,
    "preview": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, c"
  },
  {
    "path": "MainViewModel.cs",
    "chars": 13754,
    "preview": "using System;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing Syste"
  },
  {
    "path": "MainWindow.xaml",
    "chars": 12943,
    "preview": "<Window x:Class=\"fs2ff.MainWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xm"
  },
  {
    "path": "MainWindow.xaml.cs",
    "chars": 1148,
    "preview": "// ReSharper disable InconsistentNaming\n\nusing System;\nusing System.ComponentModel;\nusing System.Windows;\nusing System."
  },
  {
    "path": "Models/Attitude.cs",
    "chars": 325,
    "preview": "// ReSharper disable FieldCanBeMadeReadOnly.Global\n\nusing System.Runtime.InteropServices;\n\nnamespace fs2ff.Models\n{\n    "
  },
  {
    "path": "Models/Position.cs",
    "chars": 401,
    "preview": "// ReSharper disable FieldCanBeMadeReadOnly.Global\n\nusing System.Runtime.InteropServices;\n\nnamespace fs2ff.Models\n{\n   "
  },
  {
    "path": "Models/Traffic.cs",
    "chars": 756,
    "preview": "// ReSharper disable FieldCanBeMadeReadOnly.Global\n\nusing System.Runtime.InteropServices;\n\nnamespace fs2ff.Models\n{\n   "
  },
  {
    "path": "Preferences.Designer.cs",
    "chars": 4520,
    "preview": "//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code w"
  },
  {
    "path": "Preferences.settings",
    "chars": 1300,
    "preview": "<?xml version='1.0' encoding='utf-8'?>\n<SettingsFile xmlns=\"http://schemas.microsoft.com/VisualStudio/2004/01/settings\""
  },
  {
    "path": "README.md",
    "chars": 5509,
    "preview": "# fs2ff (Flight Simulator to ForeFlight)\n\n## What is it?\n\nThis is a utility app that connects Microsoft Flight Simulator"
  },
  {
    "path": "Resources.xaml",
    "chars": 1305,
    "preview": "<ResourceDictionary xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n                    xmlns:x=\"http:"
  },
  {
    "path": "SimConnect/DEFINITION.cs",
    "chars": 230,
    "preview": "// ReSharper disable InconsistentNaming\n// ReSharper disable UnusedMember.Global\n\nnamespace fs2ff.SimConnect\n{\n    publi"
  },
  {
    "path": "SimConnect/EVENT.cs",
    "chars": 208,
    "preview": "// ReSharper disable InconsistentNaming\n// ReSharper disable UnusedMember.Global\n\nnamespace fs2ff.SimConnect\n{\n    publi"
  },
  {
    "path": "SimConnect/ISimConnectMessageHandler.cs",
    "chars": 211,
    "preview": "using System;\n\nnamespace fs2ff.SimConnect\n{\n    public interface ISimConnectMessageHandler : IDisposable\n    {\n        "
  },
  {
    "path": "SimConnect/REQUEST.cs",
    "chars": 302,
    "preview": "// ReSharper disable InconsistentNaming\n// ReSharper disable UnusedMember.Global\n\nnamespace fs2ff.SimConnect\n{\n    publi"
  },
  {
    "path": "SimConnect/SimConnectAdapter.cs",
    "chars": 10625,
    "preview": "// ReSharper disable InconsistentNaming\n\nusing System;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing S"
  },
  {
    "path": "UpdateChecker.cs",
    "chars": 1050,
    "preview": "using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nnamespace fs2ff\n{\n    public static class UpdateChec"
  },
  {
    "path": "UpdateInformation.cs",
    "chars": 428,
    "preview": "// ReSharper disable MemberCanBePrivate.Global\n// ReSharper disable UnusedAutoPropertyAccessor.Global\n\nusing System;\n\nna"
  },
  {
    "path": "ViewModelLocator.cs",
    "chars": 152,
    "preview": "namespace fs2ff\n{\n    public class ViewModelLocator\n    {\n        public static MainViewModel Main => App.GetRequiredSe"
  },
  {
    "path": "fs2ff.csproj",
    "chars": 2894,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.WindowsDesktop\">\n\n  <PropertyGroup>\n    <OutputType>WinExe</OutputType>\n    <TargetFram"
  },
  {
    "path": "fs2ff.sln",
    "chars": 1680,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 15\nVisualStudioVersion = 15.0.26124.0\nMini"
  },
  {
    "path": "global.json",
    "chars": 80,
    "preview": "{\n  \"sdk\": {\n    \"version\": \"3.1.100\",\n    \"rollForward\": \"latestFeature\"\n  }\n}\n"
  },
  {
    "path": "publish.ps1",
    "chars": 1008,
    "preview": "param (\n    [ValidateSet('Debug','Release')]\n    [string]$Configuration = 'Release',\n    [ValidateSet('win-x64','win10-x"
  }
]

About this extraction

This page contains the full source code of the astenlund/fs2ff GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (146.0 KB), approximately 33.8k tokens, and a symbol index with 261 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!