├── .gitattributes ├── .gitignore ├── Build.ps1 ├── FenixQuartz-latest.zip ├── FenixQuartz.sln ├── FenixQuartz ├── App.xaml ├── App.xaml.cs ├── BoyerMoore.cs ├── BoyerMooreHorspool.cs ├── ConfigurationFile.cs ├── ElementManager.cs ├── FenixQuartz.config.numlvar ├── FenixQuartz.config.numoffset ├── FenixQuartz.config.string ├── FenixQuartz.csproj ├── GlobalSuppressions.cs ├── IPCManager.cs ├── IPCValue.cs ├── IPCValueLvar.cs ├── IPCValueOffset.cs ├── Logger.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── MemoryPattern.cs ├── MemoryScanner.cs ├── MemoryValue.cs ├── Microsoft.FlightSimulator.SimConnect.dll ├── MobiDefinitions.cs ├── MobiSimConnect.cs ├── NotifyIconResources.xaml ├── NotifyIconViewModel.cs ├── OutputDefinitions.cs ├── QuartzService.cs ├── SimConnect.dll └── quartz.ico ├── Installer ├── App.config ├── App.xaml ├── App.xaml.cs ├── AppPackage.zip ├── FenixQuartz.config.numlvar ├── FenixQuartz.config.numoffset ├── FenixQuartz.config.string ├── GlobalSuppressions.cs ├── Installer.csproj ├── InstallerFunctions.cs ├── InstallerWindow.xaml ├── InstallerWindow.xaml.cs ├── InstallerWorker.cs ├── Parameters.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── packages.config └── quartz.ico ├── LICENSE.txt ├── README.md ├── Restart-FenixQuartz.ps1 └── img └── icon.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # CUSTOM 7 | Releases/ 8 | *CopyToMSFS.ps1 9 | desktop.ini 10 | build.lck 11 | *Build.ps1 12 | 13 | # User-specific files 14 | *.rsuser 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | 20 | # User-specific files (MonoDevelop/Xamarin Studio) 21 | *.userprefs 22 | 23 | # Mono auto generated files 24 | mono_crash.* 25 | 26 | # Build results 27 | [Dd]ebug/ 28 | [Dd]ebugPublic/ 29 | [Rr]elease/ 30 | [Rr]eleases/ 31 | x64/ 32 | x86/ 33 | [Ww][Ii][Nn]32/ 34 | [Aa][Rr][Mm]/ 35 | [Aa][Rr][Mm]64/ 36 | bld/ 37 | [Bb]in/ 38 | [Oo]bj/ 39 | [Oo]ut/ 40 | [Ll]og/ 41 | [Ll]ogs/ 42 | 43 | # Visual Studio 2015/2017 cache/options directory 44 | .vs/ 45 | # Uncomment if you have tasks that create the project's static files in wwwroot 46 | #wwwroot/ 47 | 48 | # Visual Studio 2017 auto generated files 49 | Generated\ Files/ 50 | 51 | # MSTest test Results 52 | [Tt]est[Rr]esult*/ 53 | [Bb]uild[Ll]og.* 54 | 55 | # NUnit 56 | *.VisualState.xml 57 | TestResult.xml 58 | nunit-*.xml 59 | 60 | # Build Results of an ATL Project 61 | [Dd]ebugPS/ 62 | [Rr]eleasePS/ 63 | dlldata.c 64 | 65 | # Benchmark Results 66 | BenchmarkDotNet.Artifacts/ 67 | 68 | # .NET Core 69 | project.lock.json 70 | project.fragment.lock.json 71 | artifacts/ 72 | 73 | # ASP.NET Scaffolding 74 | ScaffoldingReadMe.txt 75 | 76 | # StyleCop 77 | StyleCopReport.xml 78 | 79 | # Files built by Visual Studio 80 | *_i.c 81 | *_p.c 82 | *_h.h 83 | *.ilk 84 | *.meta 85 | *.obj 86 | *.iobj 87 | *.pch 88 | *.pdb 89 | *.ipdb 90 | *.pgc 91 | *.pgd 92 | *.rsp 93 | *.sbr 94 | *.tlb 95 | *.tli 96 | *.tlh 97 | *.tmp 98 | *.tmp_proj 99 | *_wpftmp.csproj 100 | *.log 101 | *.vspscc 102 | *.vssscc 103 | .builds 104 | *.pidb 105 | *.svclog 106 | *.scc 107 | 108 | # Chutzpah Test files 109 | _Chutzpah* 110 | 111 | # Visual C++ cache files 112 | ipch/ 113 | *.aps 114 | *.ncb 115 | *.opendb 116 | *.opensdf 117 | *.sdf 118 | *.cachefile 119 | *.VC.db 120 | *.VC.VC.opendb 121 | 122 | # Visual Studio profiler 123 | *.psess 124 | *.vsp 125 | *.vspx 126 | *.sap 127 | 128 | # Visual Studio Trace Files 129 | *.e2e 130 | 131 | # TFS 2012 Local Workspace 132 | $tf/ 133 | 134 | # Guidance Automation Toolkit 135 | *.gpState 136 | 137 | # ReSharper is a .NET coding add-in 138 | _ReSharper*/ 139 | *.[Rr]e[Ss]harper 140 | *.DotSettings.user 141 | 142 | # TeamCity is a build add-in 143 | _TeamCity* 144 | 145 | # DotCover is a Code Coverage Tool 146 | *.dotCover 147 | 148 | # AxoCover is a Code Coverage Tool 149 | .axoCover/* 150 | !.axoCover/settings.json 151 | 152 | # Coverlet is a free, cross platform Code Coverage Tool 153 | coverage*.json 154 | coverage*.xml 155 | coverage*.info 156 | 157 | # Visual Studio code coverage results 158 | *.coverage 159 | *.coveragexml 160 | 161 | # NCrunch 162 | _NCrunch_* 163 | .*crunch*.local.xml 164 | nCrunchTemp_* 165 | 166 | # MightyMoose 167 | *.mm.* 168 | AutoTest.Net/ 169 | 170 | # Web workbench (sass) 171 | .sass-cache/ 172 | 173 | # Installshield output folder 174 | [Ee]xpress/ 175 | 176 | # DocProject is a documentation generator add-in 177 | DocProject/buildhelp/ 178 | DocProject/Help/*.HxT 179 | DocProject/Help/*.HxC 180 | DocProject/Help/*.hhc 181 | DocProject/Help/*.hhk 182 | DocProject/Help/*.hhp 183 | DocProject/Help/Html2 184 | DocProject/Help/html 185 | 186 | # Click-Once directory 187 | publish/ 188 | 189 | # Publish Web Output 190 | *.[Pp]ublish.xml 191 | *.azurePubxml 192 | # Note: Comment the next line if you want to checkin your web deploy settings, 193 | # but database connection strings (with potential passwords) will be unencrypted 194 | *.pubxml 195 | *.publishproj 196 | 197 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 198 | # checkin your Azure Web App publish settings, but sensitive information contained 199 | # in these scripts will be unencrypted 200 | PublishScripts/ 201 | 202 | # NuGet Packages 203 | *.nupkg 204 | # NuGet Symbol Packages 205 | *.snupkg 206 | # The packages folder can be ignored because of Package Restore 207 | **/[Pp]ackages/* 208 | # except build/, which is used as an MSBuild target. 209 | !**/[Pp]ackages/build/ 210 | # Uncomment if necessary however generally it will be regenerated when needed 211 | #!**/[Pp]ackages/repositories.config 212 | # NuGet v3's project.json files produces more ignorable files 213 | *.nuget.props 214 | *.nuget.targets 215 | 216 | # Microsoft Azure Build Output 217 | csx/ 218 | *.build.csdef 219 | 220 | # Microsoft Azure Emulator 221 | ecf/ 222 | rcf/ 223 | 224 | # Windows Store app package directories and files 225 | AppPackages/ 226 | BundleArtifacts/ 227 | Package.StoreAssociation.xml 228 | _pkginfo.txt 229 | *.appx 230 | *.appxbundle 231 | *.appxupload 232 | 233 | # Visual Studio cache files 234 | # files ending in .cache can be ignored 235 | *.[Cc]ache 236 | # but keep track of directories ending in .cache 237 | !?*.[Cc]ache/ 238 | 239 | # Others 240 | ClientBin/ 241 | ~$* 242 | *~ 243 | *.dbmdl 244 | *.dbproj.schemaview 245 | *.jfm 246 | *.pfx 247 | *.publishsettings 248 | orleans.codegen.cs 249 | 250 | # Including strong name files can present a security risk 251 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 252 | #*.snk 253 | 254 | # Since there are multiple workflows, uncomment next line to ignore bower_components 255 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 256 | #bower_components/ 257 | 258 | # RIA/Silverlight projects 259 | Generated_Code/ 260 | 261 | # Backup & report files from converting an old project file 262 | # to a newer Visual Studio version. Backup files are not needed, 263 | # because we have git ;-) 264 | _UpgradeReport_Files/ 265 | Backup*/ 266 | UpgradeLog*.XML 267 | UpgradeLog*.htm 268 | ServiceFabricBackup/ 269 | *.rptproj.bak 270 | 271 | # SQL Server files 272 | *.mdf 273 | *.ldf 274 | *.ndf 275 | 276 | # Business Intelligence projects 277 | *.rdl.data 278 | *.bim.layout 279 | *.bim_*.settings 280 | *.rptproj.rsuser 281 | *- [Bb]ackup.rdl 282 | *- [Bb]ackup ([0-9]).rdl 283 | *- [Bb]ackup ([0-9][0-9]).rdl 284 | 285 | # Microsoft Fakes 286 | FakesAssemblies/ 287 | 288 | # GhostDoc plugin setting file 289 | *.GhostDoc.xml 290 | 291 | # Node.js Tools for Visual Studio 292 | .ntvs_analysis.dat 293 | node_modules/ 294 | 295 | # Visual Studio 6 build log 296 | *.plg 297 | 298 | # Visual Studio 6 workspace options file 299 | *.opt 300 | 301 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 302 | *.vbw 303 | 304 | # Visual Studio LightSwitch build output 305 | **/*.HTMLClient/GeneratedArtifacts 306 | **/*.DesktopClient/GeneratedArtifacts 307 | **/*.DesktopClient/ModelManifest.xml 308 | **/*.Server/GeneratedArtifacts 309 | **/*.Server/ModelManifest.xml 310 | _Pvt_Extensions 311 | 312 | # Paket dependency manager 313 | .paket/paket.exe 314 | paket-files/ 315 | 316 | # FAKE - F# Make 317 | .fake/ 318 | 319 | # CodeRush personal settings 320 | .cr/personal 321 | 322 | # Python Tools for Visual Studio (PTVS) 323 | __pycache__/ 324 | *.pyc 325 | 326 | # Cake - Uncomment if you are using it 327 | # tools/** 328 | # !tools/packages.config 329 | 330 | # Tabs Studio 331 | *.tss 332 | 333 | # Telerik's JustMock configuration file 334 | *.jmconfig 335 | 336 | # BizTalk build output 337 | *.btp.cs 338 | *.btm.cs 339 | *.odx.cs 340 | *.xsd.cs 341 | 342 | # OpenCover UI analysis results 343 | OpenCover/ 344 | 345 | # Azure Stream Analytics local run output 346 | ASALocalRun/ 347 | 348 | # MSBuild Binary and Structured Log 349 | *.binlog 350 | 351 | # NVidia Nsight GPU debugger configuration file 352 | *.nvuser 353 | 354 | # MFractors (Xamarin productivity tool) working folder 355 | .mfractor/ 356 | 357 | # Local History for Visual Studio 358 | .localhistory/ 359 | 360 | # BeatPulse healthcheck temp database 361 | healthchecksdb 362 | 363 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 364 | MigrationBackup/ 365 | 366 | # Ionide (cross platform F# VS Code tools) working folder 367 | .ionide/ 368 | 369 | # Fody - auto-generated XML schema 370 | FodyWeavers.xsd -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | $projdir = "C:\Users\Fragtality\source\repos\FenixQuartz" 2 | $instBinDir = $projdir + "\Installer\bin\Release\app.publish" 3 | $version = "v1.8.0" 4 | 5 | #Create Lock 6 | cd $projdir 7 | if (-not (Test-Path -Path "build.lck")) { 8 | "lock" | Out-File -File "build.lck" 9 | } 10 | else { 11 | exit 0 12 | } 13 | 14 | cd "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64" 15 | #WT2GSX 16 | Write-Host "Building FenixQuartz" 17 | .\msbuild.exe ($projdir + "\FenixQuartz.sln") /t:FenixQuartz:rebuild /p:Configuration="Release" /p:BuildProjectReferences=false | Out-Null 18 | 19 | #Installer 20 | Write-Host "Building Installer" 21 | .\msbuild.exe ($projdir + "\FenixQuartz.sln") /t:Installer:rebuild /p:Configuration="Release" /p:BuildProjectReferences=false | Out-Null 22 | 23 | Copy-Item -Path ($instBinDir + "\Installer.exe") -Destination ($projdir + "\Releases\FenixQuartz-Installer-" + $version + ".exe") -Force 24 | 25 | #Remove lock 26 | cd $projdir 27 | if ((Test-Path -Path "build.lck")) { 28 | Remove-Item -Path "build.lck" 29 | } -------------------------------------------------------------------------------- /FenixQuartz-latest.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fragtality/FenixQuartz/f13c6d696e2c5809f41a8c43ff43c80097cce218/FenixQuartz-latest.zip -------------------------------------------------------------------------------- /FenixQuartz.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32526.322 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FenixQuartz", "FenixQuartz\FenixQuartz.csproj", "{7C52FCDC-2E51-4615-A2A2-49AFA487A7EC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Installer", "Installer\Installer.csproj", "{BF3DD08A-7547-4352-B1DD-9A343D757421}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {7C52FCDC-2E51-4615-A2A2-49AFA487A7EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {7C52FCDC-2E51-4615-A2A2-49AFA487A7EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {7C52FCDC-2E51-4615-A2A2-49AFA487A7EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {7C52FCDC-2E51-4615-A2A2-49AFA487A7EC}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {BF3DD08A-7547-4352-B1DD-9A343D757421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {BF3DD08A-7547-4352-B1DD-9A343D757421}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {BF3DD08A-7547-4352-B1DD-9A343D757421}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {BF3DD08A-7547-4352-B1DD-9A343D757421}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {BE60710D-0D4B-470F-A362-B7FDFF0DF790} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /FenixQuartz/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /FenixQuartz/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using H.NotifyIcon; 2 | using Serilog; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.IO; 7 | using System.Reflection; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Threading; 11 | 12 | namespace FenixQuartz 13 | { 14 | public partial class App : Application 15 | { 16 | public static bool devGUI; 17 | public static string FenixExecutable; 18 | public static string logFilePath; 19 | public static string logLevel; 20 | public static bool waitForConnect; 21 | public static int offsetBase; 22 | public static bool rawValues; 23 | public static bool useLvars; 24 | public static int updateIntervall; 25 | public static bool scaleMachValue; 26 | public static string altScaleDelim; 27 | public static bool addFcuMode; 28 | public static bool ooMode; 29 | public static string lvarPrefix; 30 | public static string groupName = "FenixQuartz"; 31 | 32 | public static new App Current => Application.Current as App; 33 | public static string ConfigFilePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\FenixQuartz\FenixQuartz.config"; 34 | public static string AppDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\FenixQuartz\bin"; 35 | public static ConfigurationFile ConfigurationFile = new(); 36 | 37 | public static bool CancellationRequested { get; set; } = false; 38 | public static bool RestartRequested { get; set; } = false; 39 | public static bool ServiceExited { get; set; } = false; 40 | 41 | private TaskbarIcon notifyIcon; 42 | public static QuartzService Service; 43 | 44 | protected static void LoadConfiguration() 45 | { 46 | ConfigurationFile.LoadConfiguration(); 47 | devGUI = Convert.ToBoolean(ConfigurationFile.GetSetting("debugGUI", "true")); 48 | FenixExecutable = Convert.ToString(ConfigurationFile.GetSetting("FenixExecutable", "FenixSystem")); 49 | logFilePath = @"..\log\" + Convert.ToString(ConfigurationFile.GetSetting("logFilePath", "FenixQuartz.log")); 50 | logLevel = Convert.ToString(ConfigurationFile.GetSetting("logLevel", "Debug")); 51 | waitForConnect = Convert.ToBoolean(ConfigurationFile.GetSetting("waitForConnect", "true")); 52 | offsetBase = Convert.ToInt32(ConfigurationFile.GetSetting("offsetBase", "0x5408"), 16); 53 | rawValues = Convert.ToBoolean(ConfigurationFile.GetSetting("rawValues", "false")); 54 | useLvars = Convert.ToBoolean(ConfigurationFile.GetSetting("useLvars", "false")); 55 | updateIntervall = Convert.ToInt32(ConfigurationFile.GetSetting("updateIntervall", "100")); 56 | scaleMachValue = Convert.ToBoolean(ConfigurationFile.GetSetting("scaleMachValue", "false")); 57 | altScaleDelim = Convert.ToString(ConfigurationFile.GetSetting("altScaleDelim", " ")); 58 | addFcuMode = Convert.ToBoolean(ConfigurationFile.GetSetting("addFcuMode", "true")); 59 | ooMode = Convert.ToBoolean(ConfigurationFile.GetSetting("ooMode", "false")); 60 | lvarPrefix = Convert.ToString(ConfigurationFile.GetSetting("lvarPrefix", "FNX2PLD_")); 61 | } 62 | 63 | protected override void OnStartup(StartupEventArgs e) 64 | { 65 | base.OnStartup(e); 66 | 67 | if (Process.GetProcessesByName("FenixQuartz").Length > 1) 68 | { 69 | MessageBox.Show("FenixQuartz is already running!", "Critical Error", MessageBoxButton.OK, MessageBoxImage.Error); 70 | Application.Current.Shutdown(); 71 | return; 72 | } 73 | 74 | Directory.SetCurrentDirectory(AppDir); 75 | 76 | if (!File.Exists(ConfigFilePath)) 77 | { 78 | ConfigFilePath = Directory.GetCurrentDirectory() + @"\FenixQuartz.config"; 79 | if (!File.Exists(ConfigFilePath)) 80 | { 81 | MessageBox.Show("No Configuration File found! Closing ...", "Critical Error", MessageBoxButton.OK, MessageBoxImage.Error); 82 | Application.Current.Shutdown(); 83 | return; 84 | } 85 | } 86 | 87 | LoadConfiguration(); 88 | InitLog(); 89 | InitSystray(); 90 | 91 | Service = new(); 92 | Task.Run(Service.Run); 93 | 94 | var timer = new DispatcherTimer 95 | { 96 | Interval = TimeSpan.FromSeconds(1) 97 | }; 98 | timer.Tick += OnTick; 99 | timer.Start(); 100 | 101 | MainWindow = new MainWindow(); 102 | } 103 | 104 | protected override void OnExit(ExitEventArgs e) 105 | { 106 | Logger.Log(LogLevel.Information, "App:OnExit", "FenixQuartz exiting ..."); 107 | 108 | CancellationRequested = true; 109 | notifyIcon?.Dispose(); 110 | base.OnExit(e); 111 | } 112 | 113 | protected void OnTick(object sender, EventArgs e) 114 | { 115 | if (ServiceExited) 116 | { 117 | Logger.Log(LogLevel.Information, "App:OnTick", "Received Signal that Service has exited"); 118 | Current.Shutdown(); 119 | } 120 | } 121 | 122 | protected static void InitLog() 123 | { 124 | LoggerConfiguration loggerConfiguration = new LoggerConfiguration().WriteTo.File(logFilePath, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 3, 125 | outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message} {NewLine}{Exception}"); 126 | if (logLevel == "Warning") 127 | loggerConfiguration.MinimumLevel.Warning(); 128 | else if (logLevel == "Debug") 129 | loggerConfiguration.MinimumLevel.Debug(); 130 | else 131 | loggerConfiguration.MinimumLevel.Information(); 132 | Log.Logger = loggerConfiguration.CreateLogger(); 133 | Log.Information($"-----------------------------------------------------------------------"); 134 | Logger.Log(LogLevel.Information, "App:InitLog", $"FenixQuartz started! Log Level: {logLevel} Log File: {logFilePath}"); 135 | } 136 | 137 | protected void InitSystray() 138 | { 139 | Logger.Log(LogLevel.Information, "App:InitSystray", $"Creating SysTray Icon ..."); 140 | notifyIcon = (TaskbarIcon)FindResource("NotifyIcon"); 141 | notifyIcon.Icon = GetIcon("quartz.ico"); 142 | notifyIcon.ForceCreate(false); 143 | } 144 | 145 | public static Icon GetIcon(string filename) 146 | { 147 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"FenixQuartz.{filename}"); 148 | return new Icon(stream); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /FenixQuartz/BoyerMoore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FenixQuartz 4 | { 5 | public static class BoyerMoore 6 | { 7 | public static int IndexOf(byte[] haystack, byte[] needle) 8 | { 9 | if (needle.Length == 0) 10 | { 11 | return -1; 12 | } 13 | 14 | int[] charTable = MakeCharTable(needle); 15 | int[] offsetTable = MakeOffsetTable(needle); 16 | for (int i = needle.Length - 1; i < haystack.Length;) 17 | { 18 | int j; 19 | for (j = needle.Length - 1; needle[j] == haystack[i]; --i, --j) 20 | { 21 | if (j == 0) 22 | { 23 | return i; 24 | } 25 | } 26 | 27 | i += Math.Max(offsetTable[needle.Length - 1 - j], charTable[haystack[i]]); 28 | } 29 | 30 | return -1; 31 | } 32 | 33 | private static int[] MakeCharTable(byte[] needle) 34 | { 35 | const int ALPHABET_SIZE = 256; 36 | int[] table = new int[ALPHABET_SIZE]; 37 | for (int i = 0; i < table.Length; ++i) 38 | { 39 | table[i] = needle.Length; 40 | } 41 | 42 | for (int i = 0; i < needle.Length - 1; ++i) 43 | { 44 | table[needle[i]] = needle.Length - 1 - i; 45 | } 46 | 47 | return table; 48 | } 49 | 50 | private static int[] MakeOffsetTable(byte[] needle) 51 | { 52 | int[] table = new int[needle.Length]; 53 | int lastPrefixPosition = needle.Length; 54 | for (int i = needle.Length - 1; i >= 0; --i) 55 | { 56 | if (IsPrefix(needle, i + 1)) 57 | { 58 | lastPrefixPosition = i + 1; 59 | } 60 | 61 | table[needle.Length - 1 - i] = lastPrefixPosition - i + needle.Length - 1; 62 | } 63 | 64 | for (int i = 0; i < needle.Length - 1; ++i) 65 | { 66 | int slen = SuffixLength(needle, i); 67 | table[slen] = needle.Length - 1 - i + slen; 68 | } 69 | 70 | return table; 71 | } 72 | 73 | private static bool IsPrefix(byte[] needle, int p) 74 | { 75 | for (int i = p, j = 0; i < needle.Length; ++i, ++j) 76 | { 77 | if (needle[i] != needle[j]) 78 | { 79 | return false; 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | private static int SuffixLength(byte[] needle, int p) 87 | { 88 | int len = 0; 89 | for (int i = p, j = needle.Length - 1; i >= 0 && needle[i] == needle[j]; --i, --j) 90 | { 91 | len += 1; 92 | } 93 | 94 | return len; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /FenixQuartz/BoyerMooreHorspool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FenixQuartz 6 | { 7 | public static class BoyerMooreHorspool 8 | { 9 | private static int[] CreateMatchingsTable((byte, bool)[] patternTuple) 10 | { 11 | var skipTable = new int[512]; 12 | var wildcards = patternTuple.Select(x => x.Item2).ToArray(); 13 | var lastIndex = patternTuple.Length - 1; 14 | 15 | var diff = lastIndex - Math.Max(Array.LastIndexOf(wildcards, false), 0); 16 | if (diff == 0) 17 | { 18 | diff = 1; 19 | } 20 | 21 | for (var i = 0; i < skipTable.Length; i++) 22 | { 23 | skipTable[i] = diff; 24 | } 25 | 26 | for (var i = lastIndex - diff; i < lastIndex; i++) 27 | { 28 | skipTable[patternTuple[i].Item1] = lastIndex - i; 29 | } 30 | 31 | return skipTable; 32 | } 33 | 34 | public static List SearchPattern(byte[] data, (byte, bool)[] patternTuple, int patternLength, int offset = 0x0) 35 | { 36 | if (!data.Any() || patternLength < 0) 37 | { 38 | throw new ArgumentException("Data or Pattern is empty"); 39 | } 40 | 41 | if (data.Length < patternLength) 42 | { 43 | throw new ArgumentException("Data cannot be smaller than the Pattern"); 44 | } 45 | 46 | var lastPatternIndex = patternTuple.Length - 1; 47 | var skipTable = CreateMatchingsTable(patternTuple); 48 | var adressList = new List(); 49 | 50 | for (var i = 0; i <= data.Length - patternTuple.Length; i += Math.Max(skipTable[data[i + lastPatternIndex] & 0xFF], 1)) 51 | { 52 | for (var j = lastPatternIndex; !patternTuple[j].Item2 || data[i + j] == patternTuple[j].Item1; --j) 53 | { 54 | if (j == 0) 55 | { 56 | adressList.Add(i + offset); 57 | break; 58 | } 59 | } 60 | } 61 | 62 | return adressList; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /FenixQuartz/ConfigurationFile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Xml; 4 | 5 | namespace FenixQuartz 6 | { 7 | public class ConfigurationFile 8 | { 9 | private readonly Dictionary appSettings = new(); 10 | private XmlDocument xmlDoc = new(); 11 | 12 | public string this[string key] 13 | { 14 | get => GetSetting(key); 15 | set => SetSetting(key, value); 16 | } 17 | 18 | public void LoadConfiguration() 19 | { 20 | xmlDoc = new(); 21 | xmlDoc.LoadXml(File.ReadAllText(App.ConfigFilePath)); 22 | 23 | XmlNode xmlSettings = xmlDoc.ChildNodes[1]; 24 | appSettings.Clear(); 25 | foreach(XmlNode child in xmlSettings.ChildNodes) 26 | appSettings.Add(child.Attributes["key"].Value, child.Attributes["value"].Value); 27 | } 28 | 29 | public void SaveConfiguration() 30 | { 31 | foreach (XmlNode child in xmlDoc.ChildNodes[1]) 32 | child.Attributes["value"].Value = appSettings[child.Attributes["key"].Value]; 33 | 34 | xmlDoc.Save(App.ConfigFilePath); 35 | } 36 | 37 | public string GetSetting(string key, string defaultValue = "") 38 | { 39 | if (appSettings.TryGetValue(key, out string value)) 40 | return value; 41 | else 42 | { 43 | XmlNode newNode = xmlDoc.CreateElement("add"); 44 | 45 | XmlAttribute attribute = xmlDoc.CreateAttribute("key"); 46 | attribute.Value = key; 47 | newNode.Attributes.Append(attribute); 48 | 49 | attribute = xmlDoc.CreateAttribute("value"); 50 | attribute.Value = defaultValue; 51 | newNode.Attributes.Append(attribute); 52 | 53 | xmlDoc.ChildNodes[1].AppendChild(newNode); 54 | appSettings.Add(key, defaultValue); 55 | SaveConfiguration(); 56 | 57 | return defaultValue; 58 | } 59 | } 60 | 61 | public void SetSetting(string key, string value) 62 | { 63 | if (appSettings.ContainsKey(key)) 64 | { 65 | appSettings[key] = value; 66 | SaveConfiguration(); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /FenixQuartz/ElementManager.cs: -------------------------------------------------------------------------------- 1 | using FSUIPC; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | 6 | namespace FenixQuartz 7 | { 8 | public class ElementManager : IDisposable 9 | { 10 | //public Dictionary MemoryPatterns; 11 | //public Dictionary MemoryValues; 12 | protected List Definitions; 13 | 14 | protected Dictionary IPCValues; 15 | //protected MemoryScanner Scanner; 16 | public static readonly NumberFormatInfo formatInfo = new CultureInfo("en-US").NumberFormat; 17 | 18 | public bool isLightTest = false; 19 | public bool isAltManaged = false; 20 | public bool isHdgManaged = false; 21 | public bool isHdgDashed = false; 22 | public bool isSpdManaged = false; 23 | public bool isSpdDashed = false; 24 | public bool isModeSpd = false; 25 | public bool isModeHdgVs = false; 26 | public bool isVsDashed = false; 27 | public bool fcuIsPowered = false; 28 | public bool isBaroStd = false; 29 | 30 | 31 | public ElementManager(List definitions) 32 | { 33 | IPCValues = new(); 34 | //MemoryValues = new(); 35 | Definitions = definitions; 36 | 37 | //// MEMORY PATTERNS 38 | //MemoryPatterns = new() 39 | //{ 40 | // //{ "FCU-2", new MemoryPattern("00 00 00 00 CE 05 00 00 FF FF FF FF 00 00 00 80") }, 41 | // //{ "ISIS-1", new MemoryPattern("49 00 53 00 49 00 53 00 20 00 70 00 6F 00 77 00 65 00 72 00 65 00 64 00") }, 42 | // //{ "ISIS-2", new MemoryPattern("46 00 65 00 6E 00 69 00 78 00 42 00 72 00 61 00 6B 00 65 00 46 00 61 00 6E 00 73 00") }, 43 | //}; 44 | 45 | //InitializeScanner(); 46 | 47 | 48 | //// MEMORY VALUES 49 | //RUDDER 50 | //AddMemoryValue("rudderDashed1", MemoryPatterns["FCU-2"], -0x4D4C, 1, "bool"); //B_FC_RUDDER_TRIM_DASHED 51 | //AddMemoryValue("rudderDashed1", MemoryPatterns["FCU-2"], -0x4D1C, 1, "bool"); //B_FC_RUDDER_TRIM_DASHED 52 | //AddMemoryValue("rudderDashed2", MemoryPatterns["FCU-2"], -0x4D64, 1, "bool"); 53 | //AddMemoryValue("rudderDashed2", MemoryPatterns["FCU-2"], -0x4D1C, 1, "bool"); 54 | 55 | //// STRING VALUES - StreamDeck 56 | if (!App.rawValues) 57 | { 58 | foreach (var def in Definitions) 59 | AddIpcOffset(def.ID, def.Type, def.Size, def.Offset); 60 | } 61 | //// RAW VALUES (Offset) 62 | else if (!App.useLvars) 63 | { 64 | foreach (var def in Definitions) 65 | AddIpcOffset(def.ID, def.Type, def.Size, def.Offset); 66 | } 67 | //// RAW VALUES (L-Var) 68 | else 69 | { 70 | foreach (var def in Definitions) 71 | AddIpcLvar(def.ID); 72 | } 73 | 74 | IPCManager.SimConnect.SubscribeLvar("S_OH_IN_LT_ANN_LT"); 75 | IPCManager.SimConnect.SubscribeLvar("B_FCU_POWER"); 76 | IPCManager.SimConnect.SubscribeLvar("I_FCU_TRACK_FPA_MODE"); 77 | IPCManager.SimConnect.SubscribeLvar("I_FCU_HEADING_VS_MODE"); 78 | IPCManager.SimConnect.SubscribeLvar("N_FCU_SPEED"); 79 | IPCManager.SimConnect.SubscribeLvar("I_FCU_SPEED_MODE"); 80 | IPCManager.SimConnect.SubscribeLvar("I_FCU_SPEED_MANAGED"); 81 | IPCManager.SimConnect.SubscribeLvar("B_FCU_SPEED_DASHED"); 82 | IPCManager.SimConnect.SubscribeLvar("N_FCU_HEADING"); 83 | IPCManager.SimConnect.SubscribeLvar("I_FCU_HEADING_MANAGED"); 84 | IPCManager.SimConnect.SubscribeLvar("B_FCU_HEADING_DASHED"); 85 | IPCManager.SimConnect.SubscribeLvar("N_FCU_ALTITUDE"); 86 | IPCManager.SimConnect.SubscribeLvar("I_FCU_ALTITUDE_MANAGED"); 87 | IPCManager.SimConnect.SubscribeLvar("S_FCU_ALTITUDE_SCALE"); 88 | IPCManager.SimConnect.SubscribeLvar("N_FCU_VS"); 89 | IPCManager.SimConnect.SubscribeLvar("B_FCU_VERTICALSPEED_DASHED"); 90 | IPCManager.SimConnect.SubscribeLvar("S_FCU_EFIS1_BARO_MODE"); 91 | IPCManager.SimConnect.SubscribeLvar("S_FCU_EFIS1_BARO_STD"); 92 | IPCManager.SimConnect.SubscribeLvar("B_FCU_EFIS1_BARO_STD"); 93 | IPCManager.SimConnect.SubscribeLvar("B_MIP_ISFD_BARO_STD"); 94 | IPCManager.SimConnect.SubscribeLvar("B_MIP_ISFD_BARO_INCH"); 95 | IPCManager.SimConnect.SubscribeLvar("N_MIP_ISFD_BARO_HPA"); 96 | IPCManager.SimConnect.SubscribeLvar("N_MIP_ISFD_BARO_INCH"); 97 | IPCManager.SimConnect.SubscribeLvar("N_MIP_CLOCK_UTC"); 98 | IPCManager.SimConnect.SubscribeLvar("N_MIP_CLOCK_ELAPSED"); 99 | IPCManager.SimConnect.SubscribeLvar("N_MIP_CLOCK_CHRONO"); 100 | IPCManager.SimConnect.SubscribeLvar("N_FREQ_XPDR_SELECTED"); 101 | IPCManager.SimConnect.SubscribeLvar("N_FREQ_STANDBY_XPDR_SELECTED"); 102 | IPCManager.SimConnect.SubscribeLvar("N_PED_XPDR_CHAR_DISPLAYED"); 103 | IPCManager.SimConnect.SubscribeLvar("N_ELEC_VOLT_BAT_1"); 104 | IPCManager.SimConnect.SubscribeLvar("N_ELEC_VOLT_BAT_2"); 105 | IPCManager.SimConnect.SubscribeLvar("N_FC_RUDDER_TRIM_DECIMAL"); 106 | IPCManager.SimConnect.SubscribeLvar("B_FC_RUDDER_TRIM_DASHED"); 107 | IPCManager.SimConnect.SubscribeSimVar("KOHLSMAN SETTING MB:1", "Millibars"); 108 | SubscribeRmpVars("1"); 109 | SubscribeRmpVars("2"); 110 | } 111 | 112 | private static void SubscribeRmpVars(string com) 113 | { 114 | IPCManager.SimConnect.SubscribeLvar($"N_PED_RMP{com}_ACTIVE"); 115 | IPCManager.SimConnect.SubscribeLvar($"N_PED_RMP{com}_STDBY"); 116 | IPCManager.SimConnect.SubscribeLvar($"I_PED_RMP{com}_VOR"); 117 | IPCManager.SimConnect.SubscribeLvar($"I_PED_RMP{com}_ILS"); 118 | IPCManager.SimConnect.SubscribeLvar($"I_PED_RMP{com}_ADF"); 119 | IPCManager.SimConnect.SubscribeLvar($"I_PED_RMP{com}_HF1"); 120 | IPCManager.SimConnect.SubscribeLvar($"I_PED_RMP{com}_HF2"); 121 | } 122 | 123 | //private void AddMemoryValue(string id, MemoryPattern pattern, long offset, int size, string type, bool castInt = false) 124 | //{ 125 | // MemoryValues.Add(id, new MemoryValue(id, pattern, offset, size, type, castInt)); 126 | //} 127 | 128 | private void AddIpcOffset(string id, string type, int size, int offset) 129 | { 130 | IPCValues.Add(id, new IPCValueOffset(id, offset, type, size)); 131 | } 132 | 133 | private void AddIpcLvar(string id) 134 | { 135 | IPCValues.Add(id, new IPCValueLvar(id)); 136 | } 137 | 138 | //private void InitializeScanner() 139 | //{ 140 | // //Process fenixProc = Process.GetProcessesByName(App.FenixExecutable).FirstOrDefault(); 141 | // //if (fenixProc != null) 142 | // //{ 143 | // // Scanner = new MemoryScanner(fenixProc); 144 | // //} 145 | // //else 146 | // //{ 147 | // // throw new NullReferenceException("Fenix Proc is NULL!"); 148 | // //} 149 | 150 | // //Logger.Log(LogLevel.Information, "ElementManager:InitializeScanner", $"Running Pattern Scan ... (Patterns#: {MemoryPatterns.Count})"); 151 | // //Scanner.SearchPatterns(MemoryPatterns.Values.ToList()); 152 | //} 153 | 154 | public void Dispose() 155 | { 156 | foreach (var value in IPCValues.Values) 157 | { 158 | if (!App.useLvars && value is IPCValueOffset) 159 | (value as IPCValueOffset).Offset.Disconnect(); 160 | } 161 | 162 | if (IPCManager.SimConnect != null && IPCManager.SimConnect.IsConnected) 163 | IPCManager.SimConnect.UnsubscribeAll(); 164 | 165 | IPCValues.Clear(); 166 | 167 | //foreach (var value in MemoryValues.Values) 168 | //{ 169 | // value.Dispose(); 170 | //} 171 | //MemoryValues.Clear(); 172 | //MemoryPatterns.Clear(); 173 | 174 | //Scanner = null; 175 | 176 | GC.SuppressFinalize(this); 177 | } 178 | 179 | private void UpdateSimVars() 180 | { 181 | isLightTest = IPCManager.SimConnect.ReadLvar("S_OH_IN_LT_ANN_LT") == 2; 182 | fcuIsPowered = IPCManager.SimConnect.ReadLvar("B_FCU_POWER") == 1; 183 | isSpdManaged = IPCManager.SimConnect.ReadLvar("I_FCU_SPEED_MANAGED") == 1; 184 | isSpdDashed = IPCManager.SimConnect.ReadLvar("B_FCU_SPEED_DASHED") == 1; 185 | isAltManaged = IPCManager.SimConnect.ReadLvar("I_FCU_ALTITUDE_MANAGED") == 1; 186 | isHdgManaged = IPCManager.SimConnect.ReadLvar("I_FCU_HEADING_MANAGED") == 1; 187 | isHdgDashed = IPCManager.SimConnect.ReadLvar("B_FCU_HEADING_DASHED") == 1; 188 | isModeSpd = IPCManager.SimConnect.ReadLvar("I_FCU_SPEED_MODE") == 1; 189 | isModeHdgVs = IPCManager.SimConnect.ReadLvar("I_FCU_HEADING_VS_MODE") == 1; 190 | isVsDashed = IPCManager.SimConnect.ReadLvar("B_FCU_VERTICALSPEED_DASHED") == 1; 191 | } 192 | 193 | public bool GenerateValues() 194 | { 195 | try 196 | { 197 | //if (!Scanner.UpdateBuffers(MemoryValues)) 198 | //{ 199 | // Logger.Log(LogLevel.Error, "ElementManager:GenerateValues", $"UpdateBuffers() failed"); 200 | // return false; 201 | //} 202 | UpdateSimVars(); 203 | 204 | UpdateFCU(); 205 | UpdateISIS(); 206 | UpdateCom("1"); 207 | UpdateCom("2"); 208 | UpdateXpdr(); 209 | UpdateBatteries(); 210 | UpdateRudder(); 211 | UpdateClock(); 212 | UpdateBaro(); 213 | 214 | if (!App.useLvars) 215 | FSUIPCConnection.Process(App.groupName); 216 | 217 | return true; 218 | } 219 | catch (Exception ex) 220 | { 221 | Logger.Log(LogLevel.Error, "ElementManager:GenerateValues", $"Exception '{ex.GetType()}' - '{ex.Message}' ({ex.StackTrace})"); 222 | return false; 223 | } 224 | } 225 | 226 | private void UpdateFCU() 227 | { 228 | if (App.rawValues) 229 | { 230 | UpdateRawFCU(); 231 | return; 232 | } 233 | bool isAltHundred = IPCManager.SimConnect.ReadLvar("S_FCU_ALTITUDE_SCALE") == 0; 234 | 235 | //SPEED 236 | string result = ""; 237 | if (fcuIsPowered) 238 | { 239 | if (isLightTest) 240 | { 241 | if (App.addFcuMode) 242 | result = "8888\n888*"; 243 | else 244 | result = "888*"; 245 | } 246 | else 247 | { 248 | if (App.addFcuMode) 249 | { 250 | if (isModeSpd) 251 | result = "SPD\n"; 252 | else 253 | result = "MACH\n"; 254 | } 255 | 256 | int value = (int)IPCManager.SimConnect.ReadLvar("N_FCU_SPEED"); 257 | 258 | if (isSpdDashed) 259 | { 260 | if (isModeSpd) 261 | result += "---"; 262 | else 263 | result += "-.--"; 264 | } 265 | else if (isModeSpd) 266 | result += value.ToString("D3"); 267 | else 268 | result += "0." + value.ToString(); 269 | 270 | if (isSpdManaged) 271 | result += "*"; 272 | } 273 | } 274 | IPCValues["fcuSpdStr"].SetValue(result); 275 | 276 | //HDG 277 | result = ""; 278 | if (fcuIsPowered) 279 | { 280 | if (isLightTest) 281 | { 282 | if (App.addFcuMode) 283 | result = "888\n888*"; 284 | else 285 | result = "888*"; 286 | } 287 | else 288 | { 289 | if (App.addFcuMode) 290 | { 291 | if (isModeHdgVs) 292 | result = "HDG\n"; 293 | else 294 | result = "TRK\n"; 295 | } 296 | 297 | if (isHdgDashed) 298 | result += "---"; 299 | else 300 | result += ((int)IPCManager.SimConnect.ReadLvar("N_FCU_HEADING")).ToString("D3"); 301 | 302 | if (isHdgManaged) 303 | result += "*"; 304 | } 305 | } 306 | IPCValues["fcuHdgStr"].SetValue(result); 307 | 308 | //ALT 309 | result = ""; 310 | if (fcuIsPowered) 311 | { 312 | if (isLightTest) 313 | result = "88888*"; 314 | else 315 | { 316 | result = ((int)IPCManager.SimConnect.ReadLvar("N_FCU_ALTITUDE")).ToString("D5"); 317 | if (isAltHundred && !string.IsNullOrEmpty(App.altScaleDelim)) 318 | result = result.Insert(2, App.altScaleDelim); 319 | if (isAltManaged) 320 | result += "*"; 321 | } 322 | } 323 | IPCValues["fcuAltStr"].SetValue(result); 324 | 325 | //VS 326 | result = ""; 327 | if (fcuIsPowered) 328 | { 329 | if (isLightTest) 330 | { 331 | if (App.addFcuMode) 332 | result = "888\n+8888"; 333 | else 334 | result = "+8888"; 335 | } 336 | else 337 | { 338 | if (App.addFcuMode) 339 | { 340 | if (isModeHdgVs) 341 | result = "V/S\n"; 342 | else 343 | result = "FPA\n"; 344 | } 345 | 346 | int vs = (int)IPCManager.SimConnect.ReadLvar("N_FCU_VS"); 347 | if (isVsDashed) 348 | { 349 | if (isModeHdgVs) 350 | result += "-----"; 351 | else 352 | result += "--.-"; 353 | } 354 | else if (isModeHdgVs) 355 | { 356 | if (vs >= 0) 357 | result += "+"; 358 | 359 | if (!App.ooMode) 360 | result += vs.ToString("D4"); 361 | else 362 | { 363 | string tmp = vs.ToString("D4"); 364 | if (vs >= 0) 365 | result += tmp[0..2] + "oo"; 366 | else 367 | result += tmp[0..3] + "oo"; 368 | } 369 | } 370 | else //fpa 371 | { 372 | float fpa = vs / 1000.0f; 373 | if (fpa >= 0.0f) 374 | result += "+"; 375 | 376 | result += fpa.ToString("F1", formatInfo); 377 | } 378 | } 379 | } 380 | IPCValues["fcuVsStr"].SetValue(result); 381 | } 382 | 383 | private void UpdateRawFCU() 384 | { 385 | float fvalue = IPCManager.SimConnect.ReadLvar("N_FCU_SPEED"); 386 | 387 | if (App.scaleMachValue && !isModeSpd) 388 | fvalue /= 100.0f; 389 | 390 | IPCValues["fcuSpd"].SetValue(fvalue); 391 | if (isSpdDashed) 392 | IPCValues["fcuSpdDashed"].SetValue((byte)1); 393 | else 394 | IPCValues["fcuSpdDashed"].SetValue((byte)0); 395 | 396 | //HDG 397 | IPCValues["fcuHdg"].SetValue((int)IPCManager.SimConnect.ReadLvar("N_FCU_HEADING")); 398 | if (isHdgDashed) 399 | IPCValues["fcuHdgDashed"].SetValue((byte)1); 400 | else 401 | IPCValues["fcuHdgDashed"].SetValue((byte)0); 402 | 403 | 404 | //ALT 405 | IPCValues["fcuAlt"].SetValue((int)IPCManager.SimConnect.ReadLvar("N_FCU_ALTITUDE")); 406 | 407 | //VS 408 | float vs = IPCManager.SimConnect.ReadLvar("N_FCU_VS"); 409 | if (!isVsDashed) 410 | { 411 | if (!isModeHdgVs) 412 | vs = (float)Math.Round(vs / 1000.0f, 1); 413 | } 414 | else 415 | vs = 0.0f; 416 | 417 | IPCValues["fcuVs"].SetValue(vs); 418 | if (isVsDashed) 419 | IPCValues["fcuVsDashed"].SetValue((byte)1); 420 | else 421 | IPCValues["fcuVsDashed"].SetValue((byte)0); 422 | 423 | } 424 | 425 | private void UpdateISIS() 426 | { 427 | double hpa = IPCManager.SimConnect.ReadLvar("N_MIP_ISFD_BARO_HPA"); 428 | double inhg = IPCManager.SimConnect.ReadLvar("N_MIP_ISFD_BARO_INCH"); 429 | bool isInch = IPCManager.SimConnect.ReadLvar("B_MIP_ISFD_BARO_INCH") == 1; 430 | bool std = IPCManager.SimConnect.ReadLvar("B_MIP_ISFD_BARO_STD") == 1; 431 | 432 | double baro = 0; 433 | if (!App.rawValues) 434 | { 435 | string result; 436 | if (!fcuIsPowered) 437 | result = ""; 438 | else if (std) 439 | result = "STD"; 440 | else 441 | { 442 | if (!isInch) 443 | { 444 | baro = Math.Round(hpa, 0); 445 | result = string.Format("{0,4:0000}", baro); 446 | } 447 | else 448 | { 449 | baro = Math.Round(inhg, 0) / 100.0f; 450 | result = string.Format(CultureInfo.InvariantCulture.NumberFormat, "{0:F2}", baro); 451 | } 452 | } 453 | 454 | IPCValues["isisStr"].SetValue(result); 455 | } 456 | else 457 | { 458 | IPCValues["isisStd"].SetValue(std ? (byte)1 : (byte)0); 459 | IPCValues["isisBaro"].SetValue((float)baro); 460 | } 461 | } 462 | 463 | private void UpdateCom(string com) 464 | { 465 | int valueActive = (int)IPCManager.SimConnect.ReadLvar($"N_PED_RMP{com}_ACTIVE"); 466 | int valueStandby = (int)IPCManager.SimConnect.ReadLvar($"N_PED_RMP{com}_STDBY"); 467 | 468 | bool courseMode = IPCManager.SimConnect.ReadLvar($"I_PED_RMP{com}_VOR") == 1 || IPCManager.SimConnect.ReadLvar($"I_PED_RMP{com}_ILS") == 1; 469 | bool adfMode = IPCManager.SimConnect.ReadLvar($"I_PED_RMP{com}_ADF") == 1; 470 | bool hfMode = IPCManager.SimConnect.ReadLvar($"I_PED_RMP{com}_HF1") == 1 || IPCManager.SimConnect.ReadLvar($"I_PED_RMP{com}_HF2") == 1; 471 | 472 | if (!App.rawValues) 473 | { 474 | if (isLightTest) 475 | { 476 | IPCValues[$"com{com}ActiveStr"].SetValue("888888"); 477 | IPCValues[$"com{com}StandbyStr"].SetValue("888888"); 478 | } 479 | else if (courseMode) 480 | { 481 | if (valueActive > 0) 482 | { 483 | IPCValues[$"com{com}ActiveStr"].SetValue(string.Format(new CultureInfo("en-US"), "{0:F3}", valueActive / 1000.0f)); 484 | if (valueStandby < 360) 485 | IPCValues[$"com{com}StandbyStr"].SetValue("C-" + string.Format(new CultureInfo("en-US"), "{0,3:F0}", valueStandby).Replace(' ','0')); 486 | else 487 | IPCValues[$"com{com}StandbyStr"].SetValue(string.Format(new CultureInfo("en-US"), "{0:F3}", valueStandby / 1000.0f)); 488 | } 489 | else 490 | { 491 | IPCValues[$"com{com}ActiveStr"].SetValue(""); 492 | IPCValues[$"com{com}StandbyStr"].SetValue(""); 493 | } 494 | 495 | } 496 | else if (adfMode) 497 | { 498 | if (valueActive > 0) 499 | IPCValues[$"com{com}ActiveStr"].SetValue(string.Format(new CultureInfo("en-US"), "{0,4:F1}", valueActive / 100.0f).Replace(' ', '0')); 500 | else 501 | IPCValues[$"com{com}ActiveStr"].SetValue(""); 502 | 503 | if (valueStandby > 0) 504 | IPCValues[$"com{com}StandbyStr"].SetValue(string.Format(new CultureInfo("en-US"), "{0,4:F1}", valueStandby / 100.0f).Replace(' ', '0')); 505 | else 506 | IPCValues[$"com{com}StandbyStr"].SetValue(""); 507 | } 508 | else if (hfMode) 509 | { 510 | if (valueActive > 0) 511 | IPCValues[$"com{com}ActiveStr"].SetValue(string.Format(new CultureInfo("en-US"), "{0,7:F3}", valueActive / 1000.0f).Replace(' ', '0')); 512 | else 513 | IPCValues[$"com{com}ActiveStr"].SetValue(""); 514 | 515 | if (valueStandby > 0) 516 | IPCValues[$"com{com}StandbyStr"].SetValue(string.Format(new CultureInfo("en-US"), "{0,7:F3}", valueStandby / 1000.0f).Replace(' ', '0')); 517 | else 518 | IPCValues[$"com{com}StandbyStr"].SetValue(""); 519 | } 520 | else 521 | { 522 | if (valueActive == 118000) 523 | IPCValues[$"com{com}ActiveStr"].SetValue("dAtA"); 524 | else if (valueActive > 0) 525 | IPCValues[$"com{com}ActiveStr"].SetValue(string.Format(new CultureInfo("en-US"), "{0:F3}", valueActive / 1000.0f)); 526 | else 527 | IPCValues[$"com{com}ActiveStr"].SetValue(""); 528 | 529 | if (valueStandby > 0) 530 | IPCValues[$"com{com}StandbyStr"].SetValue(string.Format(new CultureInfo("en-US"), "{0:F3}", valueStandby / 1000.0f)); 531 | else 532 | IPCValues[$"com{com}StandbyStr"].SetValue(""); 533 | } 534 | } 535 | else 536 | { 537 | if (isLightTest) 538 | { 539 | IPCValues[$"com{com}Active"].SetValue(888888); 540 | IPCValues[$"com{com}Standby"].SetValue(888888); 541 | } 542 | else 543 | { 544 | IPCValues[$"com{com}Active"].SetValue(valueActive); 545 | IPCValues[$"com{com}Standby"].SetValue(valueStandby); 546 | } 547 | } 548 | } 549 | 550 | private void UpdateXpdr() 551 | { 552 | string result; 553 | int input = (int)IPCManager.SimConnect.ReadLvar("N_FREQ_STANDBY_XPDR_SELECTED"); 554 | int disp = (int)IPCManager.SimConnect.ReadLvar("N_FREQ_XPDR_SELECTED"); 555 | int digits = (int)IPCManager.SimConnect.ReadLvar("N_PED_XPDR_CHAR_DISPLAYED"); 556 | 557 | if (isLightTest && disp >= 0) 558 | result = "8888"; 559 | else if (digits == 0 || disp == -1) 560 | result = ""; 561 | else if (digits < 4 && input >= 0) 562 | result = input.ToString($"D{digits}"); 563 | else if (digits == 4) 564 | result = disp.ToString($"D{digits}"); 565 | else 566 | result = ""; 567 | 568 | if (!App.rawValues) 569 | IPCValues["xpdrStr"].SetValue(result); 570 | else 571 | { 572 | IPCValues["xpdrDigits"].SetValue((short)digits); 573 | if (disp == -1) 574 | IPCValues["xpdr"].SetValue((short)-1); 575 | else if (short.TryParse(result, out short shortValue)) 576 | IPCValues["xpdr"].SetValue(shortValue); 577 | else 578 | { 579 | IPCValues["xpdr"].SetValue((short)-1); 580 | } 581 | } 582 | } 583 | 584 | private void UpdateBatteries() 585 | { 586 | if (!App.rawValues) 587 | IPCValues["bat1Str"].SetValue(string.Format(formatInfo, "{0:F1}", IPCManager.SimConnect.ReadLvar("N_ELEC_VOLT_BAT_1"))); 588 | else 589 | IPCValues["bat1"].SetValue(IPCManager.SimConnect.ReadLvar("N_ELEC_VOLT_BAT_1")); 590 | 591 | if (!App.rawValues) 592 | IPCValues["bat2Str"].SetValue(string.Format(formatInfo, "{0:F1}", IPCManager.SimConnect.ReadLvar("N_ELEC_VOLT_BAT_2"))); 593 | else 594 | IPCValues["bat2"].SetValue(IPCManager.SimConnect.ReadLvar("N_ELEC_VOLT_BAT_2")); 595 | } 596 | 597 | private void UpdateRudder() 598 | { 599 | bool isDashed = IPCManager.SimConnect.ReadLvar("B_FC_RUDDER_TRIM_DASHED") == 1; 600 | float value = IPCManager.SimConnect.ReadLvar("N_FC_RUDDER_TRIM_DECIMAL") / 10; 601 | 602 | bool power = IPCManager.SimConnect.ReadLvar("N_PED_RMP2_ACTIVE") != -1; 603 | if (!App.rawValues) 604 | { 605 | string result; 606 | if (power) 607 | { 608 | if (!isDashed) 609 | { 610 | string space = (Math.Abs(value) >= 10 ? "" : " "); 611 | if (value <= -0.1) 612 | result = "L" + space; 613 | else if (value >= 0.1) 614 | result = "R" + space; 615 | else 616 | result = " " + space; 617 | result += string.Format(formatInfo, "{0:F1}", Math.Abs(value)); 618 | } 619 | else 620 | result = "---"; 621 | } 622 | else 623 | result = ""; 624 | 625 | IPCValues["rudderStr"].SetValue(result); 626 | } 627 | else 628 | { 629 | if (power && !isDashed) 630 | IPCValues["rudder"].SetValue(value); 631 | else if (!power) 632 | IPCValues["rudder"].SetValue((float)Math.Round(value, -1)); 633 | else if (isDashed) 634 | IPCValues["rudder"].SetValue((float)Math.Round(value, -2)); 635 | } 636 | } 637 | 638 | private void UpdateClock() 639 | { 640 | UpdateClock("N_MIP_CLOCK_CHRONO", "clockChrStr", "clockChr"); 641 | UpdateClock("N_MIP_CLOCK_ELAPSED", "clockEtStr", "clockEt"); 642 | 643 | float clock = IPCManager.SimConnect.ReadLvar("N_MIP_CLOCK_UTC"); 644 | if (!App.rawValues) 645 | { 646 | string result = ""; 647 | 648 | if (isLightTest && clock >= 0) 649 | IPCValues["clockTime"].SetValue("88:88:88"); 650 | else if (clock >= 0) 651 | { 652 | int hours = (int)(clock / 10000.0f); 653 | int minutes = (int)((clock - hours * 10000) / 100); 654 | int seconds = (int)(clock - hours * 10000 - minutes * 100); 655 | 656 | result = string.Format("{0:D2}:{1:D2}:{2:D2}", hours, minutes, seconds); 657 | } 658 | 659 | IPCValues["clockTimeStr"].SetValue(result); 660 | } 661 | else 662 | { 663 | if (isLightTest && clock >= 0) 664 | IPCValues["clockTime"].SetValue(888888); 665 | else 666 | IPCValues["clockTime"].SetValue((int)clock); 667 | } 668 | } 669 | 670 | private void UpdateClock(string lvar, string strName, string numName) 671 | { 672 | int num = (int)IPCManager.SimConnect.ReadLvar(lvar); 673 | int upper = num / 100; 674 | int lower = num % 100; 675 | 676 | if (!App.rawValues) 677 | { 678 | if (isLightTest && num >= 0) 679 | IPCValues[strName].SetValue("88:88"); 680 | else if (num >= 0) 681 | IPCValues[strName].SetValue(string.Format("{0:D2}:{1:D2}", upper, lower)); 682 | else 683 | IPCValues[strName].SetValue(""); 684 | } 685 | else 686 | { 687 | if (isLightTest && num >= 0) 688 | IPCValues[numName].SetValue(8888); 689 | else 690 | IPCValues[numName].SetValue(num); 691 | } 692 | } 693 | 694 | public void UpdateBaro() 695 | { 696 | isBaroStd = IPCManager.SimConnect.ReadLvar("B_FCU_EFIS1_BARO_STD") == 1; 697 | 698 | float pressure = IPCManager.SimConnect.ReadSimVar("KOHLSMAN SETTING MB:1", "Millibars"); 699 | bool isHpa = IPCManager.SimConnect.ReadLvar("S_FCU_EFIS1_BARO_MODE") == 1; 700 | 701 | if (!isHpa) 702 | pressure = (float)Math.Round(pressure * 0.029529983071445f, 2); 703 | else 704 | pressure = (float)Math.Round(pressure, 0); 705 | 706 | if (isLightTest) 707 | pressure = 88.88f; 708 | 709 | if (!App.rawValues) 710 | { 711 | string result; 712 | if (!fcuIsPowered) 713 | result = ""; 714 | else if (isBaroStd) 715 | result = "Std"; 716 | else if (isLightTest) 717 | result = "88.88"; 718 | else 719 | { 720 | if (isHpa) 721 | result = string.Format("{0,4:0000}", pressure); 722 | else 723 | result = string.Format(CultureInfo.InvariantCulture.NumberFormat, "{0:F2}", pressure); 724 | } 725 | 726 | IPCValues["baroCptStr"].SetValue(result); 727 | } 728 | else 729 | { 730 | if (!fcuIsPowered) 731 | IPCValues["baroCpt"].SetValue((float)-1); 732 | else 733 | IPCValues["baroCpt"].SetValue((float)pressure); 734 | 735 | if (isBaroStd) 736 | IPCValues["baroCptStd"].SetValue((byte)1); 737 | else 738 | IPCValues["baroCptStd"].SetValue((byte)0); 739 | 740 | if (isHpa) 741 | IPCValues["baroCptMb"].SetValue((byte)1); 742 | else 743 | IPCValues["baroCptMb"].SetValue((byte)0); 744 | } 745 | } 746 | 747 | //public void PrintReport() 748 | //{ 749 | // //foreach (var value in MemoryValues.Values) 750 | // //{ 751 | // // ulong location = MemoryScanner.CalculateLocation(value.Pattern.Location, value.PatternOffset); 752 | // // Logger.Log(LogLevel.Information, "ElementManager:PrintReport", $"MemoryValue <{value.ID}> is at Address 0x{location:X} ({location:d})"); 753 | // //} 754 | //} 755 | } 756 | } 757 | -------------------------------------------------------------------------------- /FenixQuartz/FenixQuartz.config.numlvar: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FenixQuartz/FenixQuartz.config.numoffset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FenixQuartz/FenixQuartz.config.string: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FenixQuartz/FenixQuartz.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net7.0-windows10.0.17763.0 6 | disable 7 | disable 8 | quartz.ico 9 | True 10 | x64 11 | False 12 | 13 | 14 | 1.8 15 | Fragtality © 2024 16 | Fragtality 17 | True 18 | FenixQuartz.App 19 | true 20 | 21 | 22 | 23 | embedded 24 | 25 | 26 | 27 | embedded 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Microsoft.FlightSimulator.SimConnect.dll 47 | true 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Always 58 | 59 | 60 | Always 61 | true 62 | 63 | 64 | Always 65 | true 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /FenixQuartz/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Usage", "CA2211:Nicht konstante Felder dürfen nicht sichtbar sein")] 9 | [assembly: SuppressMessage("Performance", "CA1822:Member als statisch markieren", Justification = "", Scope = "member", Target = "~M:FenixQuartz.MobiSimConnect.SimConnect_OnException(Microsoft.FlightSimulator.SimConnect.SimConnect,Microsoft.FlightSimulator.SimConnect.SIMCONNECT_RECV_EXCEPTION)")] 10 | [assembly: SuppressMessage("Performance", "CA1822:Member als statisch markieren", Justification = "", Scope = "member", Target = "~M:FenixQuartz.QuartzService.Wait~System.Boolean")] 11 | [assembly: SuppressMessage("Interoperability", "CA1416:Plattformkompatibilität überprüfen")] 12 | [assembly: SuppressMessage("Style", "IDE0066:Switch-Anweisung in Ausdruck konvertieren")] 13 | [assembly: SuppressMessage("Style", "IDE0059:Unnötige Zuweisung eines Werts.", Justification = "", Scope = "member", Target = "~M:FenixQuartz.MemoryScanner.SearchPatterns(System.Collections.Generic.List{FenixQuartz.MemoryPattern})")] 14 | [assembly: SuppressMessage("Style", "IDE0044:Modifizierer \"readonly\" hinzufügen", Justification = "", Scope = "member", Target = "~F:FenixQuartz.MemoryScanner.process")] 15 | [assembly: SuppressMessage("Style", "IDE0044:Modifizierer \"readonly\" hinzufügen", Justification = "", Scope = "member", Target = "~F:FenixQuartz.MemoryScanner.procHandle")] 16 | [assembly: SuppressMessage("Performance", "CA1822:Member als statisch markieren", Justification = "", Scope = "member", Target = "~M:FenixQuartz.NotifyIconViewModel.RestartScanner")] 17 | [assembly: SuppressMessage("Performance", "CA1822:Member als statisch markieren", Justification = "", Scope = "member", Target = "~M:FenixQuartz.NotifyIconViewModel.ExitApplication")] 18 | [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:FenixQuartz.NotifyIconViewModel.ShowWindow")] 19 | [assembly: SuppressMessage("Style", "IDE0251:Member als \"readonly\" festlegen", Justification = "", Scope = "member", Target = "~M:FenixQuartz.ClientDataString.Set(System.Byte[])")] 20 | [assembly: SuppressMessage("Style", "IDE0059:Unnötige Zuweisung eines Werts.", Justification = "", Scope = "member", Target = "~M:FenixQuartz.MobiSimConnect.SendWasmCmd(System.Enum,System.Enum,System.String)")] 21 | -------------------------------------------------------------------------------- /FenixQuartz/IPCManager.cs: -------------------------------------------------------------------------------- 1 | using FSUIPC; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading; 6 | 7 | namespace FenixQuartz 8 | { 9 | public static class IPCManager 10 | { 11 | public static readonly int waitDuration = 30000; 12 | 13 | public static MobiSimConnect SimConnect { get; set; } = null; 14 | 15 | public static bool WaitForSimulator() 16 | { 17 | bool simRunning = IsSimRunning(); 18 | if (!simRunning && App.waitForConnect) 19 | { 20 | do 21 | { 22 | Logger.Log(LogLevel.Information, "IPCManager:WaitForSimulator", $"Simulator not started - waiting {waitDuration / 1000}s for Sim"); 23 | Thread.Sleep(waitDuration); 24 | } 25 | while (!IsSimRunning() && !App.CancellationRequested); 26 | 27 | return true; 28 | } 29 | else if (simRunning) 30 | { 31 | Logger.Log(LogLevel.Information, "IPCManager:WaitForSimulator", $"Simulator started"); 32 | return true; 33 | } 34 | else 35 | { 36 | Logger.Log(LogLevel.Error, "IPCManager:WaitForSimulator", $"Simulator not started - aborting"); 37 | return false; 38 | } 39 | } 40 | 41 | public static bool IsProcessRunning(string name) 42 | { 43 | Process proc = Process.GetProcessesByName(name).FirstOrDefault(); 44 | return proc != null && proc.ProcessName == name; 45 | } 46 | 47 | public static bool IsSimRunning() 48 | { 49 | return IsProcessRunning("FlightSimulator"); 50 | } 51 | 52 | public static bool WaitForConnection() 53 | { 54 | if (!IsSimRunning()) 55 | return false; 56 | 57 | SimConnect = new MobiSimConnect(); 58 | bool mobiRequested = SimConnect.Connect(); 59 | 60 | bool isFsuipcConnected; 61 | if (!App.useLvars) 62 | isFsuipcConnected = OpenSafeFSUIPC(); 63 | else 64 | isFsuipcConnected = true; 65 | 66 | int waitMS = waitDuration / 2; 67 | int countdown = 1000; 68 | if (!IsProcessRunning(App.FenixExecutable)) 69 | countdown = waitMS; 70 | if ((!App.useLvars && !isFsuipcConnected) || !SimConnect.IsConnected) 71 | { 72 | do 73 | { 74 | if (countdown == waitMS) 75 | Logger.Log(LogLevel.Information, "IPCManager:WaitForConnection", $"Connection not established - waiting {waitMS / 1000}s for Retry"); 76 | 77 | countdown -= 1000; 78 | Thread.Sleep(1000); 79 | 80 | if (!IsSimRunning()) 81 | break; 82 | 83 | if (countdown == 0) 84 | { 85 | if (!App.useLvars && !isFsuipcConnected) 86 | isFsuipcConnected = OpenSafeFSUIPC(); 87 | 88 | if (!mobiRequested) 89 | mobiRequested = SimConnect.Connect(); 90 | 91 | countdown = waitMS; 92 | } 93 | } 94 | while ((!App.useLvars && !isFsuipcConnected) || !SimConnect.IsConnected); 95 | 96 | return isFsuipcConnected && SimConnect.IsConnected && IsSimRunning(); 97 | } 98 | else if (isFsuipcConnected && SimConnect.IsConnected && IsSimRunning()) 99 | { 100 | Logger.Log(LogLevel.Information, "IPCManager:WaitForConnection", $"Connection established"); 101 | return true; 102 | } 103 | else 104 | { 105 | Logger.Log(LogLevel.Error, "IPCManager:WaitForConnection", $"Connection failed"); 106 | return false; 107 | } 108 | } 109 | 110 | public static bool OpenSafeFSUIPC() 111 | { 112 | try 113 | { 114 | if (!FSUIPCConnection.IsOpen) 115 | FSUIPCConnection.Open(); 116 | } 117 | catch (Exception ex) 118 | { 119 | Logger.Log(LogLevel.Error, "IPCManager:OpenSafeFSUIPC", $"Exception while connecting to FSUIPC ({ex.GetType()} {ex.Message})"); 120 | } 121 | 122 | return FSUIPCConnection.IsOpen; 123 | } 124 | 125 | public static bool WaitForFenixBinary() 126 | { 127 | if (!IsSimRunning()) 128 | return false; 129 | 130 | bool isRunning = IsProcessRunning(App.FenixExecutable); 131 | if (!isRunning) 132 | { 133 | do 134 | { 135 | Logger.Log(LogLevel.Information, "IPCManager:WaitForFenixBinary", $"{App.FenixExecutable} is not running - waiting {waitDuration / 2 / 1000}s for Retry"); 136 | Thread.Sleep(waitDuration / 2); 137 | 138 | isRunning = IsProcessRunning(App.FenixExecutable); 139 | } 140 | while (!isRunning && IsSimRunning() && !App.CancellationRequested); 141 | 142 | return isRunning && IsSimRunning(); 143 | } 144 | else 145 | { 146 | Logger.Log(LogLevel.Information, "IPCManager:WaitForFenixBinary", $"{App.FenixExecutable} is running"); 147 | return true; 148 | } 149 | } 150 | 151 | public static bool WaitForSessionReady() 152 | { 153 | int waitDuration = 5000; 154 | SimConnect.SubscribeSimVar("CAMERA STATE", "Enum"); 155 | Thread.Sleep(250); 156 | bool isReady = IsCamReady(); 157 | while (IsSimRunning() && !isReady && !App.CancellationRequested) 158 | { 159 | Logger.Log(LogLevel.Information, "IPCManager:WaitForSessionReady", $"Session not ready - waiting {waitDuration / 1000}s for Retry"); 160 | Thread.Sleep(waitDuration); 161 | isReady = IsCamReady(); 162 | } 163 | 164 | if (!isReady) 165 | { 166 | Logger.Log(LogLevel.Error, "IPCManager:WaitForSessionReady", $"SimConnect or Simulator not available - aborting"); 167 | return false; 168 | } 169 | 170 | return true; 171 | } 172 | 173 | public static bool IsCamReady() 174 | { 175 | float value = SimConnect.ReadSimVar("CAMERA STATE", "Enum"); 176 | 177 | return value >= 2 && value <= 5; 178 | } 179 | 180 | public static void CloseSafe() 181 | { 182 | try 183 | { 184 | if (SimConnect != null) 185 | { 186 | SimConnect.Disconnect(); 187 | SimConnect = null; 188 | } 189 | 190 | if (!App.useLvars && FSUIPCConnection.IsOpen) 191 | FSUIPCConnection.Close(); 192 | } 193 | catch { } 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /FenixQuartz/IPCValue.cs: -------------------------------------------------------------------------------- 1 | namespace FenixQuartz 2 | { 3 | public class IPCValue 4 | { 5 | public virtual string ID { get; set; } = ""; 6 | public virtual string Type { get; set; } = "int"; 7 | public virtual int Size { get; set; } = 4; 8 | 9 | public IPCValue(string id, string type = "int", int size = 4) 10 | { 11 | ID = id; 12 | Type = type; 13 | Size = size; 14 | } 15 | 16 | public virtual void SetValue(object value) 17 | { 18 | 19 | } 20 | 21 | public virtual dynamic GetValue() 22 | { 23 | return 0; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FenixQuartz/IPCValueLvar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FenixQuartz 4 | { 5 | public class IPCValueLvar : IPCValue 6 | { 7 | float lastValue = float.NaN; 8 | 9 | public IPCValueLvar(string id, bool noSubscription = true) : base(App.lvarPrefix + id, "float", 4) 10 | { 11 | if (!noSubscription) 12 | IPCManager.SimConnect.SubscribeLvar(ID); 13 | } 14 | 15 | public override void SetValue(object value) 16 | { 17 | float fValue = float.NaN; 18 | if (value is byte @byte) 19 | fValue = Convert.ToSingle(@byte); 20 | if (value is short @short) 21 | fValue = Convert.ToSingle(@short); 22 | if (value is int @int) 23 | fValue = Convert.ToSingle(@int); 24 | if (value is float @single) 25 | fValue = @single; 26 | if (value is double @double) 27 | fValue = Convert.ToSingle(@double); 28 | if (value is string) 29 | float.TryParse(value as string, ElementManager.formatInfo, out fValue); 30 | 31 | if (!float.IsNaN(fValue) && fValue != lastValue) 32 | { 33 | IPCManager.SimConnect.WriteLvar(ID, fValue); 34 | lastValue = fValue; 35 | } 36 | } 37 | 38 | public override dynamic GetValue() 39 | { 40 | return IPCManager.SimConnect.ReadLvar(ID); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /FenixQuartz/IPCValueOffset.cs: -------------------------------------------------------------------------------- 1 | using FSUIPC; 2 | using System; 3 | 4 | namespace FenixQuartz 5 | { 6 | public class IPCValueOffset : IPCValue 7 | { 8 | public Offset Offset { get; set; } 9 | 10 | public IPCValueOffset(string id, int address, string type = "int", int size = 4) : base(id, type, size) 11 | { 12 | ID = id; 13 | Type = type; 14 | Size = size; 15 | if (type != "string" && type != "byte") 16 | Offset = new Offset(App.groupName, address, size, true); 17 | else if (type == "byte") 18 | Offset = new Offset(App.groupName, address, true); 19 | else 20 | Offset = new Offset(App.groupName, address, size, true); 21 | } 22 | 23 | public override void SetValue(object value) 24 | { 25 | if (Type == "byte") 26 | Offset.SetValue((byte)value); 27 | if (Type == "short") 28 | Offset.SetValue(BitConverter.GetBytes((short)value)); 29 | if (Type == "int") 30 | Offset.SetValue(BitConverter.GetBytes((int)value)); 31 | if (Type == "float") 32 | Offset.SetValue(BitConverter.GetBytes((float)value)); 33 | if (Type == "double") 34 | Offset.SetValue(BitConverter.GetBytes((double)value)); 35 | if (Type == "string") 36 | Offset.SetValue((string)value); 37 | } 38 | 39 | public override dynamic GetValue() 40 | { 41 | if (Type != "string") 42 | { 43 | if (Type == "float" || Type == "double") 44 | { 45 | switch (Size) 46 | { 47 | case 4: 48 | return Offset.GetValue(); 49 | case 8: 50 | return Offset.GetValue(); 51 | default: 52 | return 0.0f; 53 | } 54 | } 55 | else 56 | { 57 | switch (Size) 58 | { 59 | case 1: 60 | return Offset.GetValue(); 61 | case 2: 62 | return Offset.GetValue(); 63 | case 4: 64 | return Offset.GetValue(); 65 | case 8: 66 | return Offset.GetValue(); 67 | default: 68 | return 0; 69 | } 70 | } 71 | } 72 | else 73 | { 74 | return Offset.GetValue(); ; 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /FenixQuartz/Logger.cs: -------------------------------------------------------------------------------- 1 | namespace FenixQuartz 2 | { 3 | public enum LogLevel 4 | { 5 | Critical = 5, 6 | Error = 4, 7 | Warning = 3, 8 | Information = 2, 9 | Debug = 1, 10 | Verbose = 0, 11 | } 12 | 13 | public static class Logger 14 | { 15 | public static void Log(LogLevel level, string context, string message) 16 | { 17 | string entry = string.Format("[ {0,-32} ] {1}", (context.Length <= 32 ? context : context[0..32]), message.Replace("\n", "").Replace("\r", "").Replace("\t", "")); 18 | switch (level) 19 | { 20 | case LogLevel.Critical: 21 | Serilog.Log.Logger.Fatal(entry); 22 | break; 23 | case LogLevel.Error: 24 | Serilog.Log.Logger.Error(entry); 25 | break; 26 | case LogLevel.Warning: 27 | Serilog.Log.Logger.Warning(entry); 28 | break; 29 | case LogLevel.Information: 30 | Serilog.Log.Logger.Information(entry); 31 | break; 32 | case LogLevel.Debug: 33 | Serilog.Log.Logger.Debug(entry); 34 | break; 35 | case LogLevel.Verbose: 36 | Serilog.Log.Logger.Verbose(entry); 37 | break; 38 | default: 39 | Serilog.Log.Logger.Debug(entry); 40 | break; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FenixQuartz/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 39 | 47 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /FenixQuartz/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Windows; 4 | 5 | namespace FenixQuartz 6 | { 7 | public partial class MainWindow : Window 8 | { 9 | //protected DispatcherTimer timer; 10 | 11 | public MainWindow() 12 | { 13 | InitializeComponent(); 14 | 15 | string assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); 16 | assemblyVersion = assemblyVersion[0..assemblyVersion.LastIndexOf('.')]; 17 | Title += " (" + assemblyVersion + ")"; 18 | 19 | //timer = new DispatcherTimer 20 | //{ 21 | // Interval = TimeSpan.FromMilliseconds(500) 22 | //}; 23 | //timer.Tick += OnTick; 24 | } 25 | 26 | 27 | protected void OnTick(object sender, EventArgs e) 28 | { 29 | if (App.Service != null && App.Service.elementManager != null /*&& App.Service.elementManager.MemoryValues != null*/) 30 | { 31 | //var manager = App.Service.elementManager; 32 | //var values = App.Service.elementManager.MemoryValues; 33 | 34 | //isisStd1.Content = values["isisStd1"].GetValue() ?? 0; 35 | //isisBaro1.Content = values["isisBaro1"].GetValue() ?? 0; 36 | //isisStd2.Content = values["isisStd2"].GetValue() ?? 0; 37 | //isisBaro2.Content = values["isisBaro2"].GetValue() ?? 0; 38 | //isisStd3.Content = values["isisStd3"].GetValue() ?? 0; 39 | //isisBaro3.Content = values["isisBaro3"].GetValue() ?? 0; 40 | 41 | //xpdrInput.Content = values["xpdrInput"].GetValue() ?? 0; 42 | 43 | //rudderDashed1.Content = values["rudderDashed1"].GetValue() ?? 0; 44 | //rudderDashed2.Content = values["rudderDashed2"].GetValue() ?? 0; 45 | 46 | //speedV1.Content = manager.speedV1; 47 | //speedVR.Content = manager.speedVR; 48 | //speedV2.Content = manager.speedV2; 49 | //toFlex.Content = manager.toFlex; 50 | } 51 | } 52 | 53 | protected void Window_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) 54 | { 55 | //if (!IsVisible) 56 | //{ 57 | // timer.Stop(); 58 | //} 59 | //else 60 | //{ 61 | // timer.Start(); 62 | //} 63 | } 64 | 65 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) 66 | { 67 | e.Cancel = true; 68 | Hide(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /FenixQuartz/MemoryPattern.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FenixQuartz 6 | { 7 | //public class MemoryPattern 8 | //{ 9 | // public byte[] BytePattern { get; set; } 10 | // public ulong Location { get; set; } = 0; 11 | // public int MatchNumber { get; set; } = 1; 12 | 13 | // public MemoryPattern(string pattern) 14 | // { 15 | // BytePattern = ConvertPattern(pattern); 16 | // } 17 | 18 | // public MemoryPattern(string pattern, int match) 19 | // { 20 | // BytePattern = ConvertPattern(pattern); 21 | // MatchNumber = match; 22 | // } 23 | 24 | // public static byte[] ConvertPattern(string patternStr) 25 | // { 26 | // List pattern = new(); 27 | // string[] bytesStr = patternStr.Split(' '); 28 | // foreach (var hexStr in bytesStr) 29 | // { 30 | // pattern.Add(byte.Parse(hexStr, System.Globalization.NumberStyles.HexNumber)); 31 | // } 32 | // return pattern.ToArray(); 33 | // } 34 | //} 35 | public class MemoryPattern 36 | { 37 | public byte[] BytePattern { get; set; } 38 | public bool HasWildCards { get; set; } = false; 39 | public string Pattern { get; set; } 40 | public ulong Location { get; set; } = 0; 41 | public int MatchNumber { get; set; } = 1; 42 | public int Matches { get; set; } = 0; 43 | public (byte, bool)[] PatternTuple { get; set; } 44 | 45 | public MemoryPattern(string pattern) 46 | { 47 | Pattern = pattern; 48 | ConvertPattern(pattern); 49 | } 50 | 51 | public MemoryPattern(string pattern, int match) : this(pattern) 52 | { 53 | MatchNumber = match; 54 | } 55 | 56 | protected void ConvertPattern(string pattern) 57 | { 58 | PatternTuple = pattern.Split(' ') 59 | .Select(hex => hex.Contains('?') 60 | ? (byte.MinValue, false) 61 | : (Convert.ToByte(hex, 16), true)) 62 | .ToArray(); 63 | 64 | List patternList = new(); 65 | string[] bytesStr = pattern.Split(' '); 66 | foreach (var hexStr in bytesStr) 67 | { 68 | if (hexStr != "??") 69 | patternList.Add(byte.Parse(hexStr, System.Globalization.NumberStyles.HexNumber)); 70 | else 71 | patternList.Add(0xFF); 72 | } 73 | BytePattern = patternList.ToArray(); 74 | 75 | HasWildCards = pattern.Contains("??"); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /FenixQuartz/MemoryScanner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace FenixQuartz 8 | { 9 | public struct MEMORY_BASIC_INFORMATION64 10 | { 11 | public ulong BaseAddress; 12 | public ulong AllocationBase; 13 | public uint AllocationProtect; 14 | public uint __alignment1; 15 | public ulong RegionSize; 16 | public uint State; 17 | public uint Protect; 18 | public uint Type; 19 | public uint __alignment2; 20 | } 21 | 22 | public struct SYSTEM_INFO 23 | { 24 | public ushort processorArchitecture; 25 | public ushort reserved; 26 | public uint pageSize; 27 | public ulong minimumApplicationAddress; 28 | public ulong maximumApplicationAddress; 29 | public IntPtr activeProcessorMask; 30 | public uint numberOfProcessors; 31 | public uint processorType; 32 | public uint allocationGranularity; 33 | public ushort processorLevel; 34 | public ushort processorRevision; 35 | } 36 | 37 | public partial class MemoryScanner 38 | { 39 | [LibraryImport("kernel32.dll", SetLastError = true)] 40 | private static partial int OpenProcess(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); 41 | [LibraryImport("kernel32.dll", SetLastError = true)] 42 | [return: MarshalAs(UnmanagedType.Bool)] 43 | private static partial bool ReadProcessMemory(int hProcess, ulong lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead); 44 | [LibraryImport("kernel32.dll")] 45 | private static partial void GetSystemInfo(out SYSTEM_INFO lpSystemInfo); 46 | [LibraryImport("kernel32.dll", SetLastError = true)] 47 | private static partial int VirtualQueryEx(int hProcess, ulong lpAddress, out MEMORY_BASIC_INFORMATION64 lpBuffer, uint dwLength); 48 | 49 | 50 | public static readonly int PROCESS_QUERY_INFORMATION = 0x0400; 51 | public static readonly int PROCESS_VM_READ = 0x0010; 52 | public static readonly int ChunkSize = 384 * 384; 53 | 54 | private int procHandle = 0; 55 | private Process process; 56 | private SYSTEM_INFO sysInfo; 57 | private readonly Dictionary> uniquePatterns = new(); 58 | 59 | public MemoryScanner(Process proc) 60 | { 61 | sysInfo = new(); 62 | GetSystemInfo(out sysInfo); 63 | 64 | procHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, proc.Id); 65 | Logger.Log(LogLevel.Information, "MemoryScanner:MemoryScanner", $"Fenix procHandle is {procHandle}"); 66 | process = proc; 67 | } 68 | 69 | public bool IsInitialized() 70 | { 71 | return procHandle != 0; 72 | } 73 | 74 | public bool FenixIsRunning() 75 | { 76 | return process != null && !process.HasExited; 77 | } 78 | 79 | private void IntializePatterns(List patterns) 80 | { 81 | uniquePatterns.Clear(); 82 | List list; 83 | foreach (var pattern in patterns) 84 | { 85 | if (uniquePatterns.TryGetValue(pattern.Pattern, out List value)) 86 | { 87 | list = value; 88 | if (list != null) 89 | { 90 | list.Add(pattern); 91 | } 92 | else 93 | { 94 | list = new() 95 | { 96 | pattern 97 | }; 98 | uniquePatterns[pattern.Pattern] = list; 99 | } 100 | } 101 | else 102 | { 103 | list = new() 104 | { 105 | pattern 106 | }; 107 | uniquePatterns.Add(pattern.Pattern, list); 108 | } 109 | } 110 | } 111 | 112 | public void SearchPatterns(List patterns) 113 | { 114 | Stopwatch watch = new(); 115 | watch.Start(); 116 | 117 | MEMORY_BASIC_INFORMATION64 memInfo = new(); 118 | ulong addrBase; 119 | ulong addrMax = sysInfo.maximumApplicationAddress; 120 | patterns.ForEach(p => p.Matches = 0); 121 | IntializePatterns(patterns); 122 | 123 | addrBase = sysInfo.minimumApplicationAddress; 124 | 125 | while (addrBase < addrMax && VirtualQueryEx(procHandle, addrBase, out memInfo, 48) != 0 && patterns.Any(p => p.Location == 0)) 126 | { 127 | if (memInfo.Protect == 0x04 && memInfo.State == 0x00001000) 128 | { 129 | SearchRegion(memInfo.BaseAddress, memInfo.RegionSize); 130 | } 131 | addrBase += memInfo.RegionSize; 132 | } 133 | 134 | watch.Stop(); 135 | Logger.Log(LogLevel.Information, "MemoryScanner:SearchPatterns", string.Format("Pattern Search took {0}s", watch.Elapsed.TotalSeconds)); 136 | 137 | GC.Collect(); 138 | GC.WaitForPendingFinalizers(); 139 | GC.Collect(); 140 | } 141 | 142 | private void SearchRegion(ulong addrBase, ulong regionSize) 143 | { 144 | ulong addrEnd = addrBase + regionSize; 145 | byte[] memBuff = new byte[ChunkSize]; 146 | int bytesRead = 0; 147 | int result; 148 | 149 | do 150 | { 151 | if (!ReadProcessMemory(procHandle, addrBase, memBuff, ChunkSize, ref bytesRead) || bytesRead < 512) 152 | { 153 | addrBase += (ulong)ChunkSize; 154 | continue; 155 | } 156 | 157 | foreach (var patternList in uniquePatterns.Values) 158 | { 159 | if (patternList.All(p => p.Location != 0)) 160 | continue; 161 | 162 | result = -1; 163 | 164 | if (!patternList.First().HasWildCards) 165 | result = BoyerMoore.IndexOf(memBuff, patternList.First().BytePattern); 166 | else 167 | { 168 | var resultList = BoyerMooreHorspool.SearchPattern(memBuff, patternList.First().PatternTuple, patternList.First().Pattern.Length); 169 | result = resultList.Count > 0 ? resultList[0] : -1; 170 | } 171 | 172 | if (result != -1) 173 | { 174 | foreach (var pattern in patternList) 175 | { 176 | if (pattern.Location == 0) 177 | { 178 | pattern.Matches++; 179 | if (pattern.Matches == pattern.MatchNumber) 180 | pattern.Location = addrBase + (ulong)result; 181 | } 182 | } 183 | } 184 | } 185 | 186 | addrBase += (ulong)ChunkSize; 187 | } 188 | while (addrBase < addrEnd); 189 | 190 | memBuff = null; 191 | } 192 | 193 | public static ulong CalculateLocation(ulong baseAddr, long offset) 194 | { 195 | if (offset < 0) 196 | baseAddr -= (ulong)(-offset); 197 | else 198 | baseAddr += (ulong)offset; 199 | 200 | return baseAddr; 201 | } 202 | 203 | public bool UpdateBuffers(Dictionary memValues) 204 | { 205 | int bytesRead = 0; 206 | byte[] memBuff; 207 | 208 | if (!FenixIsRunning()) 209 | return false; 210 | 211 | foreach (var memValue in memValues.Values) 212 | { 213 | if (memValue.Pattern.Location == 0) 214 | continue; 215 | 216 | memBuff = new byte[memValue.Size]; 217 | if (ReadProcessMemory(procHandle, CalculateLocation(memValue.Pattern.Location, memValue.PatternOffset), memBuff, memValue.Size, ref bytesRead)) 218 | memValue.UpdateBuffer(memBuff); 219 | } 220 | 221 | return true; 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /FenixQuartz/MemoryValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace FenixQuartz 5 | { 6 | public class MemoryValue : IDisposable 7 | { 8 | public string ID { get; set; } = ""; 9 | public MemoryPattern Pattern { get; set; } 10 | public long PatternOffset { get; set; } 11 | public int Size { get; set; } 12 | public string TypeName { get; set; } = "int"; 13 | public bool CastInteger { get; set; } = false; 14 | 15 | private byte[] valueBuffer = null; 16 | 17 | public MemoryValue(string id, MemoryPattern pattern, long patternOffset, int size, string typeName, bool castInteger = false) 18 | { 19 | ID = id; 20 | Pattern = pattern; 21 | PatternOffset = patternOffset; 22 | Size = size; 23 | TypeName = typeName; 24 | CastInteger = castInteger; 25 | valueBuffer = new byte[size]; 26 | } 27 | 28 | public void UpdateBuffer(byte[] memBuffer) 29 | { 30 | if (valueBuffer != null) 31 | Array.Copy(memBuffer, valueBuffer, memBuffer.Length); 32 | else 33 | Logger.Log(LogLevel.Error, "MemoryValue:UpdateBuffer", $"Error - valueBuffer is null"); 34 | } 35 | 36 | public bool IsIntegerZero() 37 | { 38 | if (valueBuffer == null) 39 | return true; 40 | else if (BitConverter.ToInt32(valueBuffer, 0) == 0) 41 | return true; 42 | else return false; 43 | } 44 | 45 | public bool IsTinyValue() 46 | { 47 | if (valueBuffer == null) 48 | return false; 49 | 50 | if (TypeName == "double") 51 | { 52 | double dbl = BitConverter.ToDouble(valueBuffer, 0); 53 | if (dbl > -0.001 && dbl < 0.001 && dbl != 0.00) 54 | return true; 55 | else 56 | return false; 57 | } 58 | if (TypeName == "float") 59 | { 60 | double flt = BitConverter.ToSingle(valueBuffer, 0); 61 | if (flt > -0.001f && flt < 0.001f && flt != 0.00f) 62 | return true; 63 | else 64 | return false; 65 | } 66 | 67 | return false; 68 | } 69 | 70 | public virtual dynamic GetValue() 71 | { 72 | try 73 | { 74 | if (valueBuffer == null) 75 | return null; 76 | 77 | if (TypeName == "float" && !CastInteger) 78 | return BitConverter.ToSingle(valueBuffer, 0); 79 | else if (TypeName == "double" && !CastInteger) 80 | return BitConverter.ToDouble(valueBuffer, 0); 81 | else if (TypeName == "bool" || TypeName == "int" && Size == 1) 82 | return BitConverter.ToBoolean(valueBuffer, 0); 83 | else if (TypeName == "int") 84 | if (Size == 4) 85 | return BitConverter.ToInt32(valueBuffer, 0); 86 | else //if (Size == 2) 87 | return BitConverter.ToInt16(valueBuffer, 0); 88 | else if (TypeName == "long") 89 | return BitConverter.ToInt64(valueBuffer, 0); 90 | else // == string 91 | return Encoding.ASCII.GetString(valueBuffer).Replace("\0", ""); 92 | } 93 | catch (Exception ex) 94 | { 95 | Logger.Log(LogLevel.Error, "MemoryValue:GetValue", $"Exception while converting Byte Value for: {ex.Source} - {ex.Message}"); 96 | } 97 | 98 | return null; 99 | } 100 | 101 | public void Dispose() 102 | { 103 | valueBuffer = null; 104 | GC.SuppressFinalize(this); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /FenixQuartz/Microsoft.FlightSimulator.SimConnect.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fragtality/FenixQuartz/f13c6d696e2c5809f41a8c43ff43c80097cce218/FenixQuartz/Microsoft.FlightSimulator.SimConnect.dll -------------------------------------------------------------------------------- /FenixQuartz/MobiDefinitions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace FenixQuartz 5 | { 6 | public enum MOBIFLIGHT_CLIENT_DATA_ID 7 | { 8 | MOBIFLIGHT_LVARS, 9 | MOBIFLIGHT_CMD, 10 | MOBIFLIGHT_RESPONSE 11 | } 12 | 13 | public enum PILOTSDECK_CLIENT_DATA_ID 14 | { 15 | MOBIFLIGHT_LVARS = 1980, 16 | MOBIFLIGHT_CMD, 17 | MOBIFLIGHT_RESPONSE 18 | } 19 | 20 | public enum SIMCONNECT_REQUEST_ID 21 | { 22 | Dummy = 0 23 | } 24 | 25 | public enum SIMCONNECT_DEFINE_ID 26 | { 27 | Dummy = 0 28 | } 29 | 30 | public enum SIMCONNECT_NOTIFICATION_GROUP_ID 31 | { 32 | SIMCONNECT_GROUP_PRIORITY_DEFAULT, 33 | SIMCONNECT_GROUP_PRIORITY_HIGHEST 34 | } 35 | public enum MOBIFLIGHT_EVENTS 36 | { 37 | DUMMY 38 | }; 39 | 40 | public class SimVar 41 | { 42 | public UInt32 ID { get; set; } 43 | public String Name { get; set; } 44 | public float Data { get; set; } 45 | 46 | public SimVar(uint iD, float data = 0.0f) 47 | { 48 | ID = iD; 49 | Data = data; 50 | } 51 | } 52 | 53 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 54 | public struct ClientDataValue 55 | { 56 | public float data; 57 | } 58 | 59 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 60 | public struct ClientDataString 61 | { 62 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)MobiSimConnect.MOBIFLIGHT_MESSAGE_SIZE)] 63 | public byte[] data; 64 | 65 | public ClientDataString() 66 | { 67 | data = new byte[MobiSimConnect.MOBIFLIGHT_MESSAGE_SIZE]; 68 | } 69 | 70 | public void Set(byte[] txtBytes) 71 | { 72 | Array.Clear(data); 73 | Array.Copy(txtBytes, data, txtBytes.Length); 74 | } 75 | } 76 | 77 | public struct ResponseString 78 | { 79 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)MobiSimConnect.MOBIFLIGHT_MESSAGE_SIZE)] 80 | public String Data; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /FenixQuartz/MobiSimConnect.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.FlightSimulator.SimConnect; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace FenixQuartz 8 | { 9 | public class MobiSimConnect : IDisposable 10 | { 11 | public const string MOBIFLIGHT_CLIENT_DATA_NAME_COMMAND = "MobiFlight.Command"; 12 | public const string MOBIFLIGHT_CLIENT_DATA_NAME_RESPONSE = "MobiFlight.Response"; 13 | public const uint MOBIFLIGHT_MESSAGE_SIZE = 1024; 14 | 15 | public const uint WM_PILOTSDECK_SIMCONNECT = 0x1980; 16 | public const string CLIENT_NAME = "FenixQuartz"; 17 | public const string PILOTSDECK_CLIENT_DATA_NAME_SIMVAR = $"{CLIENT_NAME}.LVars"; 18 | public const string PILOTSDECK_CLIENT_DATA_NAME_COMMAND = $"{CLIENT_NAME}.Command"; 19 | public const string PILOTSDECK_CLIENT_DATA_NAME_RESPONSE = $"{CLIENT_NAME}.Response"; 20 | 21 | protected SimConnect simConnect = null; 22 | protected IntPtr simConnectHandle = IntPtr.Zero; 23 | protected Thread simConnectThread = null; 24 | private static bool cancelThread = false; 25 | 26 | protected bool isSimConnected = false; 27 | protected bool isMobiConnected = false; 28 | protected bool isReceiveRunning = false; 29 | public bool IsConnected { get { return isSimConnected && isMobiConnected; } } 30 | public bool IsReady { get { return IsConnected && isReceiveRunning; } } 31 | 32 | protected uint nextID = 1; 33 | protected const int reorderTreshold = 150; 34 | protected Dictionary addressToIndex = new(); 35 | protected Dictionary simVars = new(); 36 | 37 | public MobiSimConnect() 38 | { 39 | 40 | } 41 | 42 | public bool Connect() 43 | { 44 | try 45 | { 46 | if (isSimConnected) 47 | return true; 48 | 49 | simConnect = new SimConnect(CLIENT_NAME, simConnectHandle, WM_PILOTSDECK_SIMCONNECT, null, 0); 50 | simConnect.OnRecvOpen += new SimConnect.RecvOpenEventHandler(SimConnect_OnOpen); 51 | simConnect.OnRecvQuit += new SimConnect.RecvQuitEventHandler(SimConnect_OnQuit); 52 | simConnect.OnRecvException += new SimConnect.RecvExceptionEventHandler(SimConnect_OnException); 53 | 54 | cancelThread = false; 55 | simConnectThread = new(new ThreadStart(SimConnect_ReceiveThread)) 56 | { 57 | IsBackground = true 58 | }; 59 | simConnectHandle = new IntPtr(simConnectThread.ManagedThreadId); 60 | simConnectThread.Start(); 61 | 62 | Logger.Log(LogLevel.Information, "MobiSimConnect:Connect", $"SimConnect Connection open"); 63 | return true; 64 | } 65 | catch (Exception ex) 66 | { 67 | simConnectThread = null; 68 | simConnectHandle = IntPtr.Zero; 69 | cancelThread = true; 70 | simConnect = null; 71 | 72 | Logger.Log(LogLevel.Error, "MobiSimConnect:Connect", $"Exception while opening SimConnect! (Exception: {ex.GetType()} {ex.Message})"); 73 | } 74 | 75 | return false; 76 | } 77 | 78 | protected void SimConnect_OnOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data) 79 | { 80 | try 81 | { 82 | isSimConnected = true; 83 | simConnect.OnRecvClientData += new SimConnect.RecvClientDataEventHandler(SimConnect_OnClientData); 84 | CreateDataAreaDefaultChannel(); 85 | Logger.Log(LogLevel.Information, "MobiSimConnect:SimConnect_OnOpen", $"SimConnect OnOpen received"); 86 | } 87 | catch (Exception ex) 88 | { 89 | Logger.Log(LogLevel.Error, "MobiSimConnect:SimConnect_OnOpen", $"Exception during SimConnect OnOpen! (Exception: {ex.GetType()} {ex.Message})"); 90 | } 91 | } 92 | 93 | protected void SimConnect_ReceiveThread() 94 | { 95 | ulong ticks = 0; 96 | int delay = 100; 97 | int repeat = 5000 / delay; 98 | int errors = 0; 99 | isReceiveRunning = true; 100 | while (!cancelThread && simConnect != null && isReceiveRunning) 101 | { 102 | try 103 | { 104 | simConnect.ReceiveMessage(); 105 | 106 | if (isSimConnected && !isMobiConnected && ticks % (ulong)repeat == 0) 107 | { 108 | Logger.Log(LogLevel.Debug, "MobiSimConnect:SimConnect_ReceiveThread", $"Sending Ping to MobiFlight WASM Module"); 109 | SendMobiWasmCmd("MF.DummyCmd"); 110 | SendMobiWasmCmd("MF.Ping"); 111 | } 112 | 113 | Thread.Sleep(delay); 114 | } 115 | catch (Exception ex) 116 | { 117 | errors++; 118 | if (errors > 6) 119 | { 120 | isReceiveRunning = false; 121 | Logger.Log(LogLevel.Error, "MobiSimConnect:SimConnect_ReceiveThread", $"Maximum Errors reached, closing Receive Thread! (Exception: {ex.GetType()})"); 122 | return; 123 | } 124 | } 125 | 126 | ticks++; 127 | } 128 | isReceiveRunning = false; 129 | return; 130 | } 131 | 132 | protected void CreateDataAreaDefaultChannel() 133 | { 134 | simConnect.MapClientDataNameToID(MOBIFLIGHT_CLIENT_DATA_NAME_COMMAND, MOBIFLIGHT_CLIENT_DATA_ID.MOBIFLIGHT_CMD); 135 | 136 | simConnect.MapClientDataNameToID(MOBIFLIGHT_CLIENT_DATA_NAME_RESPONSE, MOBIFLIGHT_CLIENT_DATA_ID.MOBIFLIGHT_RESPONSE); 137 | 138 | simConnect.AddToClientDataDefinition((SIMCONNECT_DEFINE_ID)0, 0, MOBIFLIGHT_MESSAGE_SIZE, 0, 0); 139 | simConnect.RegisterStruct((SIMCONNECT_DEFINE_ID)0); 140 | simConnect.RequestClientData(MOBIFLIGHT_CLIENT_DATA_ID.MOBIFLIGHT_RESPONSE, 141 | (SIMCONNECT_REQUEST_ID)0, 142 | (SIMCONNECT_DEFINE_ID)0, 143 | SIMCONNECT_CLIENT_DATA_PERIOD.ON_SET, 144 | SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.CHANGED, 145 | 0, 146 | 0, 147 | 0); 148 | } 149 | 150 | protected void CreateDataAreaClientChannel() 151 | { 152 | simConnect.MapClientDataNameToID(PILOTSDECK_CLIENT_DATA_NAME_COMMAND, PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_CMD); 153 | 154 | simConnect.MapClientDataNameToID(PILOTSDECK_CLIENT_DATA_NAME_RESPONSE, PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_RESPONSE); 155 | 156 | simConnect.MapClientDataNameToID(PILOTSDECK_CLIENT_DATA_NAME_SIMVAR, PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_LVARS); 157 | 158 | simConnect.AddToClientDataDefinition((SIMCONNECT_DEFINE_ID)0, 0, MOBIFLIGHT_MESSAGE_SIZE, 0, 0); 159 | simConnect.RegisterStruct((SIMCONNECT_DEFINE_ID)0); 160 | simConnect.RequestClientData(PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_RESPONSE, 161 | (SIMCONNECT_REQUEST_ID)0, 162 | (SIMCONNECT_DEFINE_ID)0, 163 | SIMCONNECT_CLIENT_DATA_PERIOD.ON_SET, 164 | SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.CHANGED, 165 | 0, 166 | 0, 167 | 0); 168 | } 169 | 170 | protected void SimConnect_OnClientData(SimConnect sender, SIMCONNECT_RECV_CLIENT_DATA data) 171 | { 172 | try 173 | { 174 | if (data.dwRequestID == 0) 175 | { 176 | var request = (ResponseString)data.dwData[0]; 177 | if (request.Data == "MF.Pong") 178 | { 179 | if (!isMobiConnected) 180 | { 181 | Logger.Log(LogLevel.Information, "MobiSimConnect:SimConnect_OnClientData", $"MobiFlight WASM Ping acknowledged - opening Client Connection"); 182 | SendMobiWasmCmd($"MF.Clients.Add.{CLIENT_NAME}"); 183 | } 184 | } 185 | if (request.Data == $"MF.Clients.Add.{CLIENT_NAME}.Finished") 186 | { 187 | CreateDataAreaClientChannel(); 188 | isMobiConnected = true; 189 | SendClientWasmCmd("MF.SimVars.Clear"); 190 | SendClientWasmCmd("MF.Config.MAX_VARS_PER_FRAME.Set.30"); 191 | Logger.Log(LogLevel.Information, "MobiSimConnect:SimConnect_OnClientData", $"MobiFlight WASM Client Connection opened"); 192 | } 193 | } 194 | else 195 | { 196 | var simData = (ClientDataValue)data.dwData[0]; 197 | if (simVars.ContainsKey(data.dwRequestID)) 198 | { 199 | simVars[data.dwRequestID] = simData.data; 200 | } 201 | else 202 | Logger.Log(LogLevel.Warning, "MobiSimConnect:SimConnect_OnClientData", $"The received ID '{data.dwRequestID}' is not subscribed! (Data: {data})"); 203 | } 204 | } 205 | catch (Exception ex) 206 | { 207 | Logger.Log(LogLevel.Error, "MobiSimConnect:SimConnect_OnClientData", $"Exception during SimConnect OnClientData! (Exception: {ex.GetType()}) (Data: {data})"); 208 | } 209 | } 210 | 211 | protected void SimConnect_OnQuit(SimConnect sender, SIMCONNECT_RECV data) 212 | { 213 | Disconnect(); 214 | } 215 | 216 | public void Disconnect() 217 | { 218 | try 219 | { 220 | if (isMobiConnected) 221 | SendClientWasmCmd("MF.SimVars.Clear"); 222 | 223 | cancelThread = true; 224 | if (simConnectThread != null) 225 | { 226 | simConnectThread.Interrupt(); 227 | simConnectThread.Join(500); 228 | simConnectThread = null; 229 | } 230 | 231 | if (simConnect != null) 232 | { 233 | simConnect.Dispose(); 234 | simConnect = null; 235 | simConnectHandle = IntPtr.Zero; 236 | } 237 | 238 | isSimConnected = false; 239 | isMobiConnected = false; 240 | 241 | nextID = 1; 242 | simVars.Clear(); 243 | addressToIndex.Clear(); 244 | Logger.Log(LogLevel.Information, "MobiSimConnect:Disconnect", $"SimConnect Connection closed"); 245 | } 246 | catch (Exception ex) 247 | { 248 | Logger.Log(LogLevel.Error, "MobiSimConnect:Disconnect", $"Exception during disconnecting from SimConnect! (Exception: {ex.GetType()} {ex.Message})"); 249 | } 250 | } 251 | 252 | public void Dispose() 253 | { 254 | Disconnect(); 255 | GC.SuppressFinalize(this); 256 | } 257 | 258 | private void SendClientWasmCmd(string command) 259 | { 260 | SendWasmCmd(PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_CMD, (MOBIFLIGHT_CLIENT_DATA_ID)0, command); 261 | } 262 | 263 | private void SendClientWasmDummyCmd() 264 | { 265 | SendWasmCmd(PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_CMD, (MOBIFLIGHT_CLIENT_DATA_ID)0, "MF.DummyCmd"); 266 | } 267 | 268 | private void SendMobiWasmCmd(string command) 269 | { 270 | SendWasmCmd(MOBIFLIGHT_CLIENT_DATA_ID.MOBIFLIGHT_CMD, (MOBIFLIGHT_CLIENT_DATA_ID)0, command); 271 | } 272 | 273 | private ClientDataString clientData = new(); 274 | 275 | private void SendWasmCmd(Enum cmdChannelId, Enum cmdId, string command) 276 | { 277 | byte[] txtBytes = Encoding.UTF8.GetBytes(command); 278 | clientData.Set(txtBytes); 279 | simConnect.SetClientData(cmdChannelId, cmdId, SIMCONNECT_CLIENT_DATA_SET_FLAG.DEFAULT, 0, clientData); 280 | txtBytes = null; 281 | } 282 | 283 | 284 | protected void SimConnect_OnException(SimConnect sender, SIMCONNECT_RECV_EXCEPTION data) 285 | { 286 | if (data.dwException != 3 && data.dwException != 29) 287 | Logger.Log(LogLevel.Error, "MobiSimConnect:SimConnect_OnException", $"Exception received: (Exception: {data.dwException})"); 288 | } 289 | 290 | public void SubscribeLvar(string address) 291 | { 292 | SubscribeVariable($"(L:{address})"); 293 | } 294 | 295 | public void SubscribeSimVar(string name, string unit) 296 | { 297 | SubscribeVariable($"(A:{name}, {unit})"); 298 | } 299 | 300 | protected void SubscribeVariable(string address) 301 | { 302 | try 303 | { 304 | if (!addressToIndex.ContainsKey(address)) 305 | { 306 | RegisterVariable(nextID, address); 307 | simVars.Add(nextID, 0.0f); 308 | addressToIndex.Add(address, nextID); 309 | 310 | nextID++; 311 | } 312 | else 313 | Logger.Log(LogLevel.Warning, "MobiSimConnect:SubscribeAddress", $"The Address '{address}' is already subscribed"); 314 | } 315 | catch (Exception ex) 316 | { 317 | Logger.Log(LogLevel.Error, "MobiSimConnect:SubscribeAddress", $"Exception while subscribing SimVar '{address}'! (Exception: {ex.GetType()}) (Message: {ex.Message})"); 318 | } 319 | } 320 | 321 | protected void RegisterVariable(uint ID, string address) 322 | { 323 | simConnect.AddToClientDataDefinition( 324 | (SIMCONNECT_DEFINE_ID)ID, 325 | (ID - 1) * sizeof(float), 326 | sizeof(float), 327 | 0, 328 | 0); 329 | 330 | simConnect?.RegisterStruct((SIMCONNECT_DEFINE_ID)ID); 331 | 332 | simConnect?.RequestClientData( 333 | PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_LVARS, 334 | (SIMCONNECT_REQUEST_ID)ID, 335 | (SIMCONNECT_DEFINE_ID)ID, 336 | SIMCONNECT_CLIENT_DATA_PERIOD.ON_SET, 337 | SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.CHANGED, 338 | 0, 339 | 0, 340 | 0 341 | ); 342 | 343 | SendClientWasmCmd($"MF.SimVars.Add.{address}"); 344 | } 345 | 346 | public void UnsubscribeAll() 347 | { 348 | try 349 | { 350 | SendClientWasmCmd("MF.SimVars.Clear"); 351 | nextID = 1; 352 | simVars.Clear(); 353 | addressToIndex.Clear(); 354 | } 355 | catch (Exception ex) 356 | { 357 | Logger.Log(LogLevel.Error, "MobiSimConnect:UnsubscribeAll", $"Exception while unsubscribing SimVars! (Exception: {ex.GetType()}) (Message: {ex.Message})"); 358 | } 359 | } 360 | 361 | public float ReadLvar(string address) 362 | { 363 | if (addressToIndex.TryGetValue($"(L:{address})", out uint index) && simVars.TryGetValue(index, out float value)) 364 | return value; 365 | else 366 | return 0; 367 | } 368 | 369 | public float ReadSimVar(string name, string unit) 370 | { 371 | string address = $"(A:{name}, {unit})"; 372 | if (addressToIndex.TryGetValue(address, out uint index) && simVars.TryGetValue(index, out float value)) 373 | return value; 374 | else 375 | return 0; 376 | } 377 | 378 | public void WriteLvar(string address, float value) 379 | { 380 | SendClientWasmCmd($"MF.SimVars.Set.{string.Format(ElementManager.formatInfo, "{0:G}", value)} (>L:{address})"); 381 | SendClientWasmDummyCmd(); 382 | } 383 | 384 | public void ExecuteCode(string code) 385 | { 386 | SendClientWasmCmd($"MF.SimVars.Set.{code}"); 387 | SendClientWasmDummyCmd(); 388 | } 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /FenixQuartz/NotifyIconResources.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 11 | 15 | 16 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /FenixQuartz/NotifyIconViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using CommunityToolkit.Mvvm.Input; 3 | using H.NotifyIcon; 4 | using System.Windows; 5 | 6 | namespace FenixQuartz 7 | { 8 | public partial class NotifyIconViewModel : ObservableObject 9 | { 10 | [RelayCommand] 11 | public void ShowWindow() 12 | { 13 | if (App.devGUI) 14 | { 15 | if (!Application.Current.MainWindow.IsVisible) 16 | Application.Current.MainWindow.Show(disableEfficiencyMode: true); 17 | else 18 | Application.Current.MainWindow.Hide(enableEfficiencyMode: false); 19 | } 20 | } 21 | 22 | [RelayCommand] 23 | public void RestartScanner() 24 | { 25 | App.RestartRequested = true; 26 | } 27 | 28 | [RelayCommand] 29 | public void ExitApplication() 30 | { 31 | Application.Current.Shutdown(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FenixQuartz/OutputDefinitions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FenixQuartz 4 | { 5 | public class OutputDefinition 6 | { 7 | public string ID { get; set; } = ""; 8 | public string Type { get; set; } = ""; 9 | public int Size { get; set; } = 1; 10 | public int Offset { get; set; } = 0; 11 | 12 | public OutputDefinition(string ID, string Type = "float", int Size = 4, int Offset = 0) 13 | { 14 | this.ID = ID; 15 | this.Type = Type; 16 | this.Size = Size; 17 | this.Offset = Offset; 18 | } 19 | 20 | public override string ToString() 21 | { 22 | if (!App.rawValues) 23 | return string.Format("Using FSUIPC Offset: {0,-16} 0x{1:X} Type: {2,-8} Size: {3}", $"'{ID}'", Offset, Type, Size); 24 | else if (!App.useLvars) 25 | return string.Format("Using FSUIPC Offset: {0,-16} 0x{1:X} Type: {2,-8} Size: {3}", $"'{ID}'", Offset, Type, Size); 26 | else 27 | return $"Using L-Var L:{App.lvarPrefix + ID}"; 28 | } 29 | 30 | public static OutputDefinition AddIpcOffset(string ID, string Type, int Size, ref int nextOffset) 31 | { 32 | var def = new OutputDefinition(ID, Type, Size, nextOffset); 33 | nextOffset += Size; 34 | return def; 35 | } 36 | 37 | public static List CreateDefinitions() 38 | { 39 | List definitions = new(); 40 | int nextOffset = App.offsetBase; 41 | 42 | //// STRING VALUES - StreamDeck 43 | if (!App.rawValues) 44 | { 45 | //FCU 46 | definitions.Add(AddIpcOffset("fcuSpdStr", "string", 11, ref nextOffset)); 47 | definitions.Add(AddIpcOffset("fcuHdgStr", "string", 9, ref nextOffset)); 48 | definitions.Add(AddIpcOffset("fcuAltStr", "string", 8, ref nextOffset)); 49 | definitions.Add(AddIpcOffset("fcuVsStr", "string", 10, ref nextOffset)); 50 | 51 | //ISIS 52 | definitions.Add(AddIpcOffset("isisStr", "string", 6, ref nextOffset)); 53 | 54 | //COM1+2 55 | definitions.Add(AddIpcOffset("com1ActiveStr", "string", 8, ref nextOffset)); 56 | definitions.Add(AddIpcOffset("com1StandbyStr", "string", 8, ref nextOffset)); 57 | definitions.Add(AddIpcOffset("com2ActiveStr", "string", 8, ref nextOffset)); 58 | definitions.Add(AddIpcOffset("com2StandbyStr", "string", 8, ref nextOffset)); 59 | 60 | //XPDR 61 | definitions.Add(AddIpcOffset("xpdrStr", "string", 5, ref nextOffset)); 62 | 63 | //BAT1 64 | definitions.Add(AddIpcOffset("bat1Str", "string", 5, ref nextOffset)); 65 | 66 | //BAT2 67 | definitions.Add(AddIpcOffset("bat2Str", "string", 5, ref nextOffset)); 68 | 69 | //RUDDER 70 | definitions.Add(AddIpcOffset("rudderStr", "string", 6, ref nextOffset)); 71 | 72 | //Clock, CHR + ET 73 | definitions.Add(AddIpcOffset("clockChrStr", "string", 6, ref nextOffset)); 74 | definitions.Add(AddIpcOffset("clockTimeStr", "string", 9, ref nextOffset)); 75 | definitions.Add(AddIpcOffset("clockEtStr", "string", 6, ref nextOffset)); 76 | 77 | //BARO 78 | definitions.Add(AddIpcOffset("baroCptStr", "string", 6, ref nextOffset)); 79 | } 80 | //// RAW VALUES (Offset) 81 | else if (!App.useLvars) 82 | { 83 | //FCU 84 | definitions.Add(AddIpcOffset("fcuSpd", "float", 4, ref nextOffset)); 85 | definitions.Add(AddIpcOffset("fcuHdg", "int", 4, ref nextOffset)); 86 | definitions.Add(AddIpcOffset("fcuAlt", "int", 4, ref nextOffset)); 87 | definitions.Add(AddIpcOffset("fcuVs", "float", 4, ref nextOffset)); 88 | 89 | //ISIS 90 | definitions.Add(AddIpcOffset("isisStd", "byte", 1, ref nextOffset)); 91 | definitions.Add(AddIpcOffset("isisBaro", "float", 4, ref nextOffset)); 92 | 93 | //COM1+2 94 | definitions.Add(AddIpcOffset("com1Active", "int", 4, ref nextOffset)); 95 | definitions.Add(AddIpcOffset("com1Standby", "int", 4, ref nextOffset)); 96 | definitions.Add(AddIpcOffset("com2Active", "int", 4, ref nextOffset)); 97 | definitions.Add(AddIpcOffset("com2Standby", "int", 4, ref nextOffset)); 98 | 99 | //XPDR 100 | definitions.Add(AddIpcOffset("xpdr", "short", 2, ref nextOffset)); 101 | 102 | //BAT1 103 | definitions.Add(AddIpcOffset("bat1", "float", 4, ref nextOffset)); 104 | 105 | //BAT2 106 | definitions.Add(AddIpcOffset("bat2", "float", 4, ref nextOffset)); 107 | 108 | //RUDDER 109 | definitions.Add(AddIpcOffset("rudder", "float", 4, ref nextOffset)); 110 | 111 | //FCU Dashes 112 | definitions.Add(AddIpcOffset("fcuSpdDashed", "byte", 1, ref nextOffset)); 113 | definitions.Add(AddIpcOffset("fcuHdgDashed", "byte", 1, ref nextOffset)); 114 | definitions.Add(AddIpcOffset("fcuVsDashed", "byte", 1, ref nextOffset)); 115 | 116 | //Clock, CHR + ET 117 | definitions.Add(AddIpcOffset("clockChr", "int", 4, ref nextOffset)); 118 | definitions.Add(AddIpcOffset("clockTime", "int", 4, ref nextOffset)); 119 | definitions.Add(AddIpcOffset("clockEt", "int", 4, ref nextOffset)); 120 | 121 | //XPDR Digits 122 | definitions.Add(AddIpcOffset("xpdrDigits", "short", 2, ref nextOffset)); 123 | 124 | //BARO 125 | definitions.Add(AddIpcOffset("baroCpt", "float", 4, ref nextOffset)); 126 | definitions.Add(AddIpcOffset("baroCptStd", "byte", 1, ref nextOffset)); 127 | definitions.Add(AddIpcOffset("baroCptMb", "byte", 1, ref nextOffset)); 128 | } 129 | //// RAW VALUES (L-Var) 130 | else 131 | { 132 | //FCU 133 | definitions.Add(new OutputDefinition("fcuSpd")); 134 | definitions.Add(new OutputDefinition("fcuHdg")); 135 | definitions.Add(new OutputDefinition("fcuAlt")); 136 | definitions.Add(new OutputDefinition("fcuVs")); 137 | 138 | //FCU Dashes 139 | definitions.Add(new OutputDefinition("fcuSpdDashed")); 140 | definitions.Add(new OutputDefinition("fcuHdgDashed")); 141 | definitions.Add(new OutputDefinition("fcuVsDashed")); 142 | 143 | //ISIS 144 | definitions.Add(new OutputDefinition("isisStd")); 145 | definitions.Add(new OutputDefinition("isisBaro")); 146 | 147 | //COM1 148 | definitions.Add(new OutputDefinition("com1Active")); 149 | definitions.Add(new OutputDefinition("com1Standby")); 150 | 151 | //COM2 152 | definitions.Add(new OutputDefinition("com2Active")); 153 | definitions.Add(new OutputDefinition("com2Standby")); 154 | 155 | //XPDR 156 | definitions.Add(new OutputDefinition("xpdr")); 157 | definitions.Add(new OutputDefinition("xpdrDigits")); 158 | 159 | //BAT1 160 | definitions.Add(new OutputDefinition("bat1")); 161 | 162 | //BAT2 163 | definitions.Add(new OutputDefinition("bat2")); 164 | 165 | //RUDDER 166 | definitions.Add(new OutputDefinition("rudder")); 167 | 168 | //CHR + ET 169 | definitions.Add(new OutputDefinition("clockChr")); 170 | definitions.Add(new OutputDefinition("clockTime")); 171 | definitions.Add(new OutputDefinition("clockEt")); 172 | 173 | //BARO 174 | definitions.Add(new OutputDefinition("baroCpt")); 175 | definitions.Add(new OutputDefinition("baroCptStd")); 176 | definitions.Add(new OutputDefinition("baroCptMb")); 177 | } 178 | 179 | 180 | return definitions; 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /FenixQuartz/QuartzService.cs: -------------------------------------------------------------------------------- 1 | using FSUIPC; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | namespace FenixQuartz 10 | { 11 | public class QuartzService 12 | { 13 | public ElementManager elementManager = null; 14 | public List Definitions = null; 15 | 16 | public void Run() 17 | { 18 | try 19 | { 20 | WriteAssignmentFile(); 21 | 22 | Logger.Log(LogLevel.Information, "QuartzService:Run", $"Entering Service Loop ..."); 23 | while (!App.CancellationRequested) 24 | { 25 | if (Wait() && InitializeSession()) 26 | { 27 | ServiceLoop(); 28 | Reset(); 29 | if (App.RestartRequested) 30 | { 31 | Logger.Log(LogLevel.Information, "QuartzService:Run", $"Restart requested"); 32 | App.RestartRequested = false; 33 | } 34 | } 35 | else 36 | { 37 | if (!RetryPossible()) 38 | { 39 | App.CancellationRequested = true; 40 | App.ServiceExited = true; 41 | Logger.Log(LogLevel.Error, "QuartzService:Run", $"Session aborted, Retry not possible - exiting Program"); 42 | } 43 | else 44 | { 45 | Reset(); 46 | Logger.Log(LogLevel.Information, "QuartzService:Run", $"Session aborted, Retry possible - Waiting for new Session"); 47 | } 48 | } 49 | } 50 | 51 | Close(); 52 | 53 | } 54 | catch (Exception ex) 55 | { 56 | Logger.Log(LogLevel.Error, "QuartzService:Run", $"Critical Exception occured: {ex.Source} - {ex.Message}"); 57 | } 58 | } 59 | 60 | private bool Wait() 61 | { 62 | if (!IPCManager.WaitForSimulator()) 63 | return false; 64 | 65 | if (!IPCManager.WaitForConnection()) 66 | return false; 67 | 68 | if (!IPCManager.WaitForFenixBinary()) 69 | return false; 70 | 71 | if (!IPCManager.WaitForSessionReady()) 72 | return false; 73 | 74 | return true; 75 | } 76 | 77 | private static bool RetryPossible() 78 | { 79 | return IPCManager.IsSimRunning(); 80 | } 81 | 82 | private void Reset() 83 | { 84 | try 85 | { 86 | Logger.Log(LogLevel.Information, "QuartzService:Reset", $"Resetting Session"); 87 | IPCManager.SimConnect?.Disconnect(); 88 | IPCManager.SimConnect = null; 89 | if (!App.useLvars && FSUIPCConnection.IsOpen) 90 | FSUIPCConnection.Close(); 91 | elementManager?.Dispose(); 92 | elementManager = null; 93 | } 94 | catch (Exception ex) 95 | { 96 | Logger.Log(LogLevel.Error, "QuartzService:Reset", $"Exception during Reset ({ex.GetType()} {ex.Message})"); 97 | } 98 | } 99 | 100 | private void Close() 101 | { 102 | Reset(); 103 | IPCManager.CloseSafe(); 104 | } 105 | 106 | private bool InitializeSession() 107 | { 108 | try 109 | { 110 | elementManager = new ElementManager(Definitions); 111 | 112 | return true; 113 | } 114 | catch (Exception ex) 115 | { 116 | Logger.Log(LogLevel.Error, "QuartzService:InitializeSession", $"Exception during Intialization {ex.GetType()} {ex.Message})"); 117 | return false; 118 | } 119 | } 120 | 121 | private void ServiceLoop() 122 | { 123 | if (elementManager == null) 124 | throw new ArgumentException("ServiceLoop: ElementManager is null"); 125 | 126 | //elementManager.PrintReport(); 127 | //Service Loop 128 | Stopwatch watch = new(); 129 | int measures = 0; 130 | int averageTick = 300; 131 | 132 | try 133 | { 134 | Thread.Sleep(300); 135 | while (!App.CancellationRequested && !App.RestartRequested && IPCManager.IsProcessRunning(App.FenixExecutable) && IPCManager.IsSimRunning()) 136 | { 137 | watch.Start(); 138 | 139 | if (!elementManager.GenerateValues()) 140 | { 141 | Logger.Log(LogLevel.Error, "QuartzService:ServiceLoop", $"GenerateValues() failed"); 142 | break; 143 | } 144 | 145 | if (App.useLvars && measures % 50 == 0 || !App.useLvars && measures % 1000 == 0) 146 | { 147 | GC.Collect(); 148 | GC.WaitForPendingFinalizers(); 149 | GC.Collect(); 150 | } 151 | 152 | watch.Stop(); 153 | measures++; 154 | if (measures > averageTick) 155 | { 156 | //Logger.Log(LogLevel.Debug, "QuartzService:ServiceLoop", $"Average elapsed Time for Reading and Updating Buffers: {string.Format("{0,3:F}", (watch.Elapsed.TotalMilliseconds) / averageTick)}ms"); 157 | measures = 0; 158 | watch.Reset(); 159 | } 160 | 161 | Thread.Sleep(App.updateIntervall); 162 | } 163 | 164 | Logger.Log(LogLevel.Information, "QuartzService:ServiceLoop", $"ServiceLoop ended"); 165 | } 166 | catch (Exception ex) 167 | { 168 | Logger.Log(LogLevel.Error, "QuartzService:InitializeSession", $"Exception during ServiceLoop {ex.GetType()} {ex.Message})"); 169 | } 170 | } 171 | 172 | public void WriteAssignmentFile() 173 | { 174 | Logger.Log(LogLevel.Information, "QuartzService:WriteAssignmentFile", $"Writing Assignments.txt File ..."); 175 | Definitions = OutputDefinition.CreateDefinitions(); 176 | StringBuilder output = new(); 177 | 178 | foreach(var value in Definitions) 179 | { 180 | output.AppendLine(value.ToString()); 181 | } 182 | 183 | //output.AppendLine(""); 184 | //output.AppendLine("TO Speeds (always L-Vars):"); 185 | //output.AppendLine(App.lvarPrefix + "speedV1"); 186 | //output.AppendLine(App.lvarPrefix + "speedVR"); 187 | //output.AppendLine(App.lvarPrefix + "speedV2"); 188 | //output.AppendLine(App.lvarPrefix + "toFlex"); 189 | 190 | File.WriteAllText("..\\Assignments.txt", output.ToString()); 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /FenixQuartz/SimConnect.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fragtality/FenixQuartz/f13c6d696e2c5809f41a8c43ff43c80097cce218/FenixQuartz/SimConnect.dll -------------------------------------------------------------------------------- /FenixQuartz/quartz.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fragtality/FenixQuartz/f13c6d696e2c5809f41a8c43ff43c80097cce218/FenixQuartz/quartz.ico -------------------------------------------------------------------------------- /Installer/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Installer/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Installer/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Installer 4 | { 5 | public partial class App : Application 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Installer/AppPackage.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fragtality/FenixQuartz/f13c6d696e2c5809f41a8c43ff43c80097cce218/Installer/AppPackage.zip -------------------------------------------------------------------------------- /Installer/FenixQuartz.config.numlvar: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Installer/FenixQuartz.config.numoffset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Installer/FenixQuartz.config.string: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Installer/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0018:Inlinevariablendeklaration", Justification = "", Scope = "member", Target = "~M:Installer.InstallerFunctions.CheckVersion(System.String,System.String,System.Boolean,System.Boolean)~System.Boolean")] 9 | [assembly: SuppressMessage("Style", "IDE1006:Benennungsstile", Justification = "")] 10 | [assembly: SuppressMessage("Style", "IDE0044:Modifizierer \"readonly\" hinzufügen")] 11 | -------------------------------------------------------------------------------- /Installer/Installer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BF3DD08A-7547-4352-B1DD-9A343D757421} 8 | WinExe 9 | Installer 10 | Installer 11 | v4.8 12 | 512 13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 4 15 | true 16 | true 17 | false 18 | publish\ 19 | true 20 | Disk 21 | false 22 | Foreground 23 | 7 24 | Days 25 | false 26 | false 27 | true 28 | 0 29 | 1.8.0.0 30 | false 31 | true 32 | true 33 | 34 | 35 | AnyCPU 36 | true 37 | full 38 | false 39 | bin\Debug\ 40 | DEBUG;TRACE 41 | prompt 42 | 4 43 | 44 | 45 | x64 46 | embedded 47 | true 48 | bin\Release\ 49 | TRACE 50 | prompt 51 | 4 52 | false 53 | true 54 | 55 | 56 | Installer.App 57 | 58 | 59 | quartz.ico 60 | 61 | 62 | 750C28371E108F6798D938582F37D04F064DDD1A 63 | 64 | 65 | Installer_TemporaryKey.pfx 66 | 67 | 68 | true 69 | 70 | 71 | true 72 | 73 | 74 | 75 | 76 | 77 | ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll 78 | True 79 | True 80 | 81 | 82 | 83 | ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll 84 | True 85 | True 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 4.0 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | MSBuild:Compile 103 | Designer 104 | 105 | 106 | MSBuild:Compile 107 | Designer 108 | 109 | 110 | App.xaml 111 | Code 112 | 113 | 114 | 115 | 116 | 117 | InstallerWindow.xaml 118 | Code 119 | 120 | 121 | 122 | 123 | 124 | Code 125 | 126 | 127 | True 128 | True 129 | Resources.resx 130 | 131 | 132 | True 133 | Settings.settings 134 | True 135 | 136 | 137 | ResXFileCodeGenerator 138 | Resources.Designer.cs 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | SettingsSingleFileGenerator 147 | Settings.Designer.cs 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | False 156 | Microsoft .NET Framework 4.8 %28x86 und x64%29 157 | true 158 | 159 | 160 | False 161 | .NET Framework 3.5 SP1 162 | false 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /Installer/InstallerFunctions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.IO.Compression; 8 | using System.Linq; 9 | using System.Net; 10 | using System.Reflection; 11 | using System.Runtime.InteropServices; 12 | using System.Runtime.InteropServices.ComTypes; 13 | using System.Text; 14 | using System.Text.RegularExpressions; 15 | using System.Windows; 16 | using System.Xml; 17 | 18 | namespace Installer 19 | { 20 | public enum AutoStart 21 | { 22 | REMOVE = -1, 23 | NONE = 0, 24 | FSUIPC, 25 | EXE 26 | } 27 | 28 | public enum ConfigFile 29 | { 30 | NOCHANGE = 0, 31 | STRING, 32 | NUMOFFSET, 33 | NUMLVAR 34 | } 35 | 36 | public static class InstallerFunctions 37 | { 38 | public static bool GetProcessRunning(string name) 39 | { 40 | Process proc = Process.GetProcessesByName(name).FirstOrDefault(); 41 | return proc != null && proc.ProcessName == name; 42 | } 43 | 44 | #region Install Actions 45 | public static bool AutoStartFsuipc(bool removeEntry = false) 46 | { 47 | bool result = false; 48 | string programParam = "READY"; 49 | if (CheckFSUIPC("7.4.0")) 50 | programParam = "CONNECTED"; 51 | 52 | try 53 | { 54 | string regPath = (string)Registry.GetValue(Parameters.ipcRegPath, Parameters.ipcRegInstallDirValue, null); 55 | if (!string.IsNullOrEmpty(regPath)) 56 | regPath += "\\" + "FSUIPC7.ini"; 57 | else 58 | return false; 59 | 60 | if (File.Exists(regPath)) 61 | { 62 | string fileContent = File.ReadAllText(regPath, Encoding.Default); 63 | if (!fileContent.Contains("[Programs]") && !removeEntry) 64 | { 65 | fileContent += $"\r\n[Programs]\r\nRunIf1={programParam},CLOSE,{Parameters.binPath}"; 66 | File.WriteAllText(regPath, fileContent, Encoding.Default); 67 | result = true; 68 | } 69 | else 70 | { 71 | RegexOptions regOptions = RegexOptions.Compiled | RegexOptions.Multiline; 72 | var runMatches = Regex.Matches(fileContent, @"[;]{0,1}Run(\d+).*", regOptions); 73 | int lastRun = 0; 74 | if (runMatches.Count > 0 && runMatches[runMatches.Count - 1].Groups.Count == 2) 75 | lastRun = Convert.ToInt32(runMatches[runMatches.Count - 1].Groups[1].Value); 76 | 77 | var runIfMatches = Regex.Matches(fileContent, @"[;]{0,1}RunIf(\d+).*", regOptions); 78 | int lastRunIf = 0; 79 | if (runIfMatches.Count > 0 && runIfMatches[runIfMatches.Count - 1].Groups.Count == 2) 80 | lastRunIf = Convert.ToInt32(runIfMatches[runIfMatches.Count - 1].Groups[1].Value); 81 | 82 | if (Regex.IsMatch(fileContent, @"^[;]{0,1}Run(\d+).*" + Parameters.appName + "\\.exe", regOptions)) 83 | { 84 | if (!removeEntry) 85 | fileContent = Regex.Replace(fileContent, @"^[;]{0,1}Run(\d+).*" + Parameters.appName + "\\.exe", $"RunIf{lastRunIf + 1}={programParam},CLOSE,{Parameters.binPath}", regOptions); 86 | else 87 | fileContent = Regex.Replace(fileContent, @"^[;]{0,1}Run(\d+).*" + Parameters.appName + "\\.exe", $"", regOptions); 88 | File.WriteAllText(regPath, fileContent, Encoding.Default); 89 | result = true; 90 | } 91 | else if (Regex.IsMatch(fileContent, @"^[;]{0,1}RunIf(\d+).*" + Parameters.appName + "\\.exe", regOptions)) 92 | { 93 | if (!removeEntry) 94 | fileContent = Regex.Replace(fileContent, @"^[;]{0,1}RunIf(\d+).*" + Parameters.appName + "\\.exe", $"RunIf$1={programParam},CLOSE,{Parameters.binPath}", regOptions); 95 | else 96 | fileContent = Regex.Replace(fileContent, @"^[;]{0,1}RunIf(\d+).*" + Parameters.appName + "\\.exe", $"", regOptions); 97 | File.WriteAllText(regPath, fileContent, Encoding.Default); 98 | result = true; 99 | } 100 | else 101 | { 102 | int index = -1; 103 | if (runIfMatches.Count > 0 && runMatches.Count > 0) 104 | { 105 | index = runIfMatches[runIfMatches.Count - 1].Index + runIfMatches[runIfMatches.Count - 1].Length; 106 | if (runMatches[runMatches.Count - 1].Index > runIfMatches[runIfMatches.Count - 1].Index) 107 | index = runMatches[runMatches.Count - 1].Index + runMatches[runMatches.Count - 1].Length; 108 | } 109 | else if (runIfMatches.Count > 0) 110 | index = runIfMatches[runIfMatches.Count - 1].Index + runIfMatches[runIfMatches.Count - 1].Length; 111 | else if (runMatches.Count > 0) 112 | index = runMatches[runMatches.Count - 1].Index + runMatches[runMatches.Count - 1].Length; 113 | 114 | if (index > 0 && !removeEntry) 115 | { 116 | fileContent = fileContent.Insert(index + 1, $"RunIf{lastRunIf + 1}={programParam},CLOSE,{Parameters.binPath}\r\n"); 117 | File.WriteAllText(regPath, fileContent, Encoding.Default); 118 | result = true; 119 | } 120 | else if (!removeEntry) 121 | { 122 | fileContent = Regex.Replace(fileContent, @"^\[Programs\]\r\n", $"[Programs]\r\nRunIf{lastRunIf + 1}={programParam},CLOSE,{Parameters.binPath}\r\n", regOptions); 123 | File.WriteAllText(regPath, fileContent, Encoding.Default); 124 | result = true; 125 | } 126 | } 127 | } 128 | } 129 | } 130 | catch (Exception e) 131 | { 132 | MessageBox.Show($"Exception '{e.GetType()}' during AutoStartFsuipc", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 133 | } 134 | 135 | return result; 136 | } 137 | 138 | public static bool AutoStartExe(bool removeEntry = false) 139 | { 140 | bool result = false; 141 | 142 | try 143 | { 144 | string path = Parameters.msExeStore; 145 | if (!File.Exists(path)) 146 | path = Parameters.msExeSteam; 147 | 148 | XmlDocument xmlDoc = new XmlDocument(); 149 | xmlDoc.LoadXml(File.ReadAllText(path)); 150 | 151 | bool found = false; 152 | XmlNode simbase = xmlDoc.ChildNodes[1]; 153 | List removeList = new List(); 154 | foreach (XmlNode outerNode in simbase.ChildNodes) 155 | { 156 | if (outerNode.Name == "Launch.Addon" && outerNode.InnerText.Contains(Parameters.appBinary)) 157 | { 158 | found = true; 159 | 160 | if (!removeEntry) 161 | { 162 | foreach (XmlNode innerNode in outerNode.ChildNodes) 163 | { 164 | if (innerNode.Name == "Disabled") 165 | innerNode.InnerText = "False"; 166 | else if (innerNode.Name == "Path") 167 | innerNode.InnerText = Parameters.binPath; 168 | else if (innerNode.Name == "CommandLine") 169 | innerNode.InnerText = ""; 170 | else if (innerNode.Name == "ManualLoad") 171 | innerNode.InnerText = "False"; 172 | } 173 | } 174 | else 175 | removeList.Add(outerNode); 176 | } 177 | } 178 | foreach (XmlNode node in removeList) 179 | xmlDoc.ChildNodes[1].RemoveChild(node); 180 | 181 | if (!found && !removeEntry) 182 | { 183 | XmlNode outerNode = xmlDoc.CreateElement("Launch.Addon"); 184 | 185 | XmlNode innerNode = xmlDoc.CreateElement("Disabled"); 186 | innerNode.InnerText = "False"; 187 | outerNode.AppendChild(innerNode); 188 | 189 | innerNode = xmlDoc.CreateElement("ManualLoad"); 190 | innerNode.InnerText = "False"; 191 | outerNode.AppendChild(innerNode); 192 | 193 | innerNode = xmlDoc.CreateElement("Name"); 194 | innerNode.InnerText = Parameters.appName; 195 | outerNode.AppendChild(innerNode); 196 | 197 | innerNode = xmlDoc.CreateElement("Path"); 198 | innerNode.InnerText = Parameters.binPath; 199 | outerNode.AppendChild(innerNode); 200 | 201 | xmlDoc.ChildNodes[1].AppendChild(outerNode); 202 | } 203 | 204 | xmlDoc.Save(path); 205 | result = true; 206 | } 207 | catch (Exception e) 208 | { 209 | MessageBox.Show($"Exception '{e.GetType()}' during AutoStartExe", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 210 | } 211 | 212 | return result; 213 | } 214 | 215 | public static bool PlaceDesktopLink() 216 | { 217 | bool result = false; 218 | try 219 | { 220 | IShellLink link = (IShellLink)new ShellLink(); 221 | 222 | link.SetDescription("Start " + Parameters.appName); 223 | link.SetPath(Parameters.binPath); 224 | 225 | IPersistFile file = (IPersistFile)link; 226 | string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); 227 | file.Save(Path.Combine(desktopPath, $"{Parameters.appName}.lnk"), false); 228 | result = true; 229 | } 230 | catch (Exception e) 231 | { 232 | MessageBox.Show($"Exception '{e.GetType()}' during PlaceDesktopLink", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 233 | } 234 | 235 | return result; 236 | } 237 | 238 | public static bool DeleteOldFiles() 239 | { 240 | try 241 | { 242 | if (!Directory.Exists(Parameters.binDir)) 243 | return true; 244 | 245 | Directory.Delete(Parameters.binDir, true); 246 | Directory.CreateDirectory(Parameters.binDir); 247 | 248 | return (new DirectoryInfo(Parameters.binDir)).GetFiles().Length == 0; 249 | } 250 | catch (Exception e) 251 | { 252 | MessageBox.Show($"Exception '{e.GetType()}' during RemoveOldFiles", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 253 | return false; 254 | } 255 | } 256 | 257 | public static bool ExtractZip(string extractDir = null, string zipFile = null) 258 | { 259 | try 260 | { 261 | if (zipFile == null) 262 | { 263 | using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"Installer.{Parameters.fileName}")) 264 | { 265 | ZipArchive archive = new ZipArchive(stream); 266 | archive.ExtractToDirectory(Parameters.binDir); 267 | stream.Close(); 268 | } 269 | 270 | RunCommand($"powershell -WindowStyle Hidden -Command \"dir -Path {Parameters.binDir} -Recurse | Unblock-File\""); 271 | } 272 | else 273 | { 274 | using (Stream stream = new FileStream(zipFile, FileMode.Open)) 275 | { 276 | ZipArchive archive = new ZipArchive(stream); 277 | archive.ExtractToDirectory(extractDir); 278 | stream.Close(); 279 | } 280 | } 281 | 282 | return true; 283 | } 284 | catch (Exception e) 285 | { 286 | MessageBox.Show($"Exception '{e.GetType()}' during ExtractZip", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 287 | return false; 288 | } 289 | } 290 | 291 | public static bool InstallWasm() 292 | { 293 | bool result = false; 294 | try 295 | { 296 | 297 | 298 | } 299 | catch (Exception e) 300 | { 301 | MessageBox.Show($"Exception '{e.GetType()}' during InstallWasm", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 302 | } 303 | 304 | return result; 305 | } 306 | 307 | public static bool DownloadFile(string url, string file) 308 | { 309 | bool result = false; 310 | try 311 | { 312 | var webClient = new WebClient(); 313 | webClient.DownloadFile(url, file); 314 | result = File.Exists(file); 315 | 316 | } 317 | catch (Exception e) 318 | { 319 | MessageBox.Show($"Exception '{e.GetType()}' during DownloadFile", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 320 | } 321 | 322 | return result; 323 | } 324 | #endregion 325 | 326 | #region Check Requirements 327 | public static bool CheckFSUIPC(string version = null) 328 | { 329 | bool result = false; 330 | string ipcVersion = Parameters.ipcVersion; 331 | if (!string.IsNullOrEmpty(version)) 332 | ipcVersion = version; 333 | 334 | try 335 | { 336 | string regVersion = (string)Registry.GetValue(Parameters.ipcRegPath, Parameters.ipcRegValue, null); 337 | if (!string.IsNullOrWhiteSpace(regVersion)) 338 | { 339 | regVersion = regVersion.Substring(1); 340 | int index = regVersion.IndexOf("(beta)"); 341 | if (index > 0) 342 | regVersion = regVersion.Substring(0, index).TrimEnd(); 343 | result = CheckVersion(regVersion, ipcVersion, true, false); 344 | } 345 | } 346 | catch (Exception e) 347 | { 348 | MessageBox.Show($"Exception '{e.GetType()}' during CheckFSUIPC", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 349 | } 350 | 351 | return result; 352 | } 353 | public static bool CheckVersion(string versionInstalled, string versionRequired, bool majorEqual, bool ignoreBuild) 354 | { 355 | bool majorMatch = false; 356 | bool minorMatch = false; 357 | bool patchMatch = false; 358 | 359 | string[] strInst = versionInstalled.Split('.'); 360 | string[] strReq = versionRequired.Split('.'); 361 | int vInst; 362 | int vReq; 363 | bool prevWasEqual = false; 364 | 365 | for (int i = 0; i= vReq; 378 | 379 | prevWasEqual = vInst == vReq; 380 | } 381 | 382 | //Minor 383 | if (int.TryParse(strInst[1], out vInst) && int.TryParse(strReq[1], out vReq)) 384 | { 385 | if (prevWasEqual) 386 | minorMatch = vInst >= vReq; 387 | else 388 | minorMatch = true; 389 | 390 | prevWasEqual = vInst == vReq; 391 | } 392 | 393 | //Patch 394 | if (!ignoreBuild) 395 | { 396 | if (int.TryParse(strInst[2], out vInst) && int.TryParse(strReq[2], out vReq)) 397 | { 398 | if (prevWasEqual) 399 | patchMatch = vInst >= vReq; 400 | else 401 | patchMatch = true; 402 | } 403 | } 404 | else 405 | patchMatch = true; 406 | 407 | return majorMatch && minorMatch && patchMatch; 408 | } 409 | 410 | public static bool CheckPackageVersion(string packagePath, string packageName, string version) 411 | { 412 | try 413 | { 414 | string file = packagePath + "\\" + packageName + "\\manifest.json"; 415 | if (File.Exists(file)) 416 | { 417 | string[] lines = File.ReadAllLines(file); 418 | foreach (string line in lines) 419 | { 420 | if (Parameters.wasmRegex.IsMatch(line)) 421 | { 422 | var matches = Parameters.wasmRegex.Matches(line); 423 | if (matches.Count == 1 && matches[0].Groups.Count >= 2) 424 | return CheckVersion(matches[0].Groups[1].Value, version, false, false); 425 | } 426 | } 427 | } 428 | } 429 | catch (Exception e) 430 | { 431 | MessageBox.Show($"Exception '{e.GetType()}' during CheckPackageVersion", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 432 | } 433 | 434 | return false; 435 | } 436 | 437 | public static string FindPackagePath(string confFile) 438 | { 439 | string[] lines = File.ReadAllLines(confFile); 440 | foreach (string line in lines) 441 | { 442 | if (line.StartsWith(Parameters.msStringPackage)) 443 | { 444 | return line.Replace("\"", "").Substring(Parameters.msStringPackage.Length) + "\\Community"; 445 | } 446 | } 447 | 448 | return ""; 449 | } 450 | 451 | public static bool CheckInstalledMSFS(out string packagePath) 452 | { 453 | try 454 | { 455 | if (File.Exists(Parameters.msConfigStore)) 456 | { 457 | packagePath = FindPackagePath(Parameters.msConfigStore); 458 | return !string.IsNullOrWhiteSpace(packagePath) && Directory.Exists(packagePath); 459 | } 460 | else if (File.Exists(Parameters.msConfigSteam)) 461 | { 462 | packagePath = FindPackagePath(Parameters.msConfigSteam); 463 | return !string.IsNullOrWhiteSpace(packagePath) && Directory.Exists(packagePath); 464 | } 465 | 466 | packagePath = ""; 467 | return false; 468 | } 469 | catch (Exception e) 470 | { 471 | MessageBox.Show($"Exception '{e.GetType()}' during CheckInstalledMSFS", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 472 | } 473 | 474 | packagePath = ""; 475 | return false; 476 | } 477 | 478 | public static string RunCommand(string command) 479 | { 480 | var pProcess = new Process(); 481 | pProcess.StartInfo.FileName = "cmd.exe"; 482 | pProcess.StartInfo.Arguments = "/C" + command; 483 | pProcess.StartInfo.UseShellExecute = false; 484 | pProcess.StartInfo.CreateNoWindow = true; 485 | pProcess.StartInfo.RedirectStandardOutput = true; 486 | pProcess.Start(); 487 | string strOutput = pProcess.StandardOutput.ReadToEnd(); 488 | pProcess.WaitForExit(); 489 | 490 | return strOutput ?? ""; 491 | } 492 | 493 | public static bool StringGreaterEqual(string input, int compare) 494 | { 495 | if (int.TryParse(input, NumberStyles.Number, CultureInfo.InvariantCulture, out int numA) && numA >= compare) 496 | return true; 497 | else 498 | return false; 499 | } 500 | 501 | public static bool StringEqual(string input, int compare) 502 | { 503 | if (int.TryParse(input, NumberStyles.Number, CultureInfo.InvariantCulture, out int numA) && numA == compare) 504 | return true; 505 | else 506 | return false; 507 | } 508 | 509 | public static bool StringGreater(string input, int compare) 510 | { 511 | if (int.TryParse(input, NumberStyles.Number, CultureInfo.InvariantCulture, out int numA) && numA > compare) 512 | return true; 513 | else 514 | return false; 515 | } 516 | 517 | public static bool CheckDotNet() 518 | { 519 | try 520 | { 521 | bool installedDesktop = false; 522 | 523 | string output = RunCommand("dotnet --list-runtimes"); 524 | 525 | var matches = Parameters.netDesktop.Matches(output); 526 | foreach (Match match in matches) 527 | { 528 | if (!match.Success || match.Groups.Count != 5) 529 | continue; 530 | if (!StringEqual(match.Groups[2].Value, Parameters.netMajor)) 531 | continue; 532 | else if ((StringEqual(match.Groups[3].Value, Parameters.netMinor) && StringGreaterEqual(match.Groups[4].Value, Parameters.netPatch)) 533 | || StringGreater(match.Groups[3].Value, Parameters.netMinor)) 534 | installedDesktop = true; 535 | } 536 | 537 | return installedDesktop; 538 | } 539 | catch (Exception e) 540 | { 541 | MessageBox.Show($"Exception '{e.GetType()}' during CheckDotNet", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 542 | return false; 543 | } 544 | } 545 | #endregion 546 | } 547 | 548 | [ComImport] 549 | [Guid("00021401-0000-0000-C000-000000000046")] 550 | internal class ShellLink 551 | { 552 | } 553 | 554 | [ComImport] 555 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 556 | [Guid("000214F9-0000-0000-C000-000000000046")] 557 | internal interface IShellLink 558 | { 559 | void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, int fFlags); 560 | void GetIDList(out IntPtr ppidl); 561 | void SetIDList(IntPtr pidl); 562 | void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); 563 | void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); 564 | void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); 565 | void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); 566 | void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); 567 | void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); 568 | void GetHotkey(out short pwHotkey); 569 | void SetHotkey(short wHotkey); 570 | void GetShowCmd(out int piShowCmd); 571 | void SetShowCmd(int iShowCmd); 572 | void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon); 573 | void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); 574 | void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved); 575 | void Resolve(IntPtr hwnd, int fFlags); 576 | void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); 577 | } 578 | } 579 | -------------------------------------------------------------------------------- /Installer/InstallerWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | Create Link on Desktop 16 | Remove Auto-Start 17 | Do not configure Auto-Start 18 | Auto-Start with FSUIPC 19 | Auto-Start with MSFS 20 | 21 | 22 | Do not change the App Configuration 23 | Change Configuration to String/Offset Mode 24 | Change Configuration to Raw-Value/Offset Mode 25 | Change Configuration to Raw-Value/L-Var Mode 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Installer/InstallerWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Media; 8 | using System.Windows.Threading; 9 | 10 | namespace Installer 11 | { 12 | public partial class InstallerWindow : Window 13 | { 14 | private InstallerWorker worker; 15 | private Queue messageQueue; 16 | private DispatcherTimer timer; 17 | private bool workerHasFinished = false; 18 | 19 | public InstallerWindow() 20 | { 21 | InitializeComponent(); 22 | 23 | string assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); 24 | assemblyVersion = assemblyVersion.Substring(0, assemblyVersion.LastIndexOf('.')); 25 | Title += " (" + assemblyVersion + ")"; 26 | 27 | if (Directory.Exists(Parameters.appDir)) 28 | btnInstall.Content = "Update!"; 29 | else 30 | { 31 | btnRemove.IsEnabled = false; 32 | btnRemove.Visibility = Visibility.Hidden; 33 | } 34 | 35 | messageQueue = new Queue(); 36 | worker = new InstallerWorker(messageQueue); 37 | timer = new DispatcherTimer 38 | { 39 | Interval = TimeSpan.FromMilliseconds(200) 40 | }; 41 | timer.Tick += OnTick; 42 | } 43 | 44 | protected void OnTick(object sender, EventArgs e) 45 | { 46 | while (messageQueue.Count > 0) 47 | { 48 | txtMessages.Text += messageQueue.Dequeue().ToString() + "\n"; 49 | } 50 | if (!worker.IsRunning) 51 | { 52 | timer.Stop(); 53 | workerHasFinished = true; 54 | if (worker.HasError) 55 | { 56 | lblResult.Content = "ERROR during Installation!"; 57 | lblResult.Foreground = new SolidColorBrush(Colors.Red); 58 | } 59 | else 60 | { 61 | lblResult.Content = "FINISHED successfully!"; 62 | lblResult.Foreground = new SolidColorBrush(Colors.DarkGreen); 63 | lblAvWarning.Visibility = Visibility.Visible; 64 | lblRebootWarning.Visibility = Visibility.Visible; 65 | } 66 | btnInstall.Content = "Close"; 67 | btnInstall.IsEnabled = true; 68 | Activate(); 69 | } 70 | } 71 | 72 | private void btnInstall_Click(object sender, RoutedEventArgs e) 73 | { 74 | if (!workerHasFinished) 75 | { 76 | if (InstallerFunctions.GetProcessRunning(Parameters.appName)) 77 | { 78 | MessageBox.Show($"Please stop {Parameters.appName} and try again.", $"{Parameters.appName} is running!", MessageBoxButton.OK, MessageBoxImage.Warning); 79 | return; 80 | } 81 | 82 | chkDesktopLink_Click(null, null); 83 | radio_ClickAutoStart(null, null); 84 | radio_ClickConfig(null, null); 85 | 86 | btnInstall.IsEnabled = false; 87 | btnRemove.Visibility = Visibility.Hidden; 88 | btnRemove.IsEnabled = false; 89 | timer.Start(); 90 | Task.Run(worker.Run); 91 | } 92 | else 93 | { 94 | App.Current.Shutdown(); 95 | } 96 | } 97 | 98 | private void btnRemove_Click(object sender, RoutedEventArgs e) 99 | { 100 | if (InstallerFunctions.GetProcessRunning(Parameters.appName)) 101 | { 102 | MessageBox.Show($"Please stop {Parameters.appName} and try again.", $"{Parameters.appName} is running!", MessageBoxButton.OK, MessageBoxImage.Warning); 103 | return; 104 | } 105 | else 106 | { 107 | btnRemove.IsEnabled = false; 108 | btnInstall.IsEnabled = false; 109 | 110 | try 111 | { 112 | Directory.Delete(Parameters.appDir, true); 113 | InstallerFunctions.AutoStartExe(true); 114 | InstallerFunctions.AutoStartFsuipc(true); 115 | } 116 | catch (Exception ex) 117 | { 118 | MessageBox.Show($"Exception '{ex.GetType()}' during Uninstall", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 119 | return; 120 | } 121 | 122 | lblResult.Content = "REMOVED successfully!"; 123 | lblResult.Foreground = new SolidColorBrush(Colors.DarkGreen); 124 | } 125 | } 126 | 127 | private void chkDesktopLink_Click(object sender, RoutedEventArgs e) 128 | { 129 | worker.CfgDesktopLink = chkDesktopLink.IsChecked ?? false; 130 | } 131 | 132 | private void radio_ClickAutoStart(object sender, RoutedEventArgs e) 133 | { 134 | if (radioNone.IsChecked == true) 135 | worker.CfgAutoStart = AutoStart.NONE; 136 | else if (radioFsuipc.IsChecked == true) 137 | worker.CfgAutoStart = AutoStart.FSUIPC; 138 | else if (radioExe.IsChecked == true) 139 | worker.CfgAutoStart = AutoStart.EXE; 140 | else if (radioRemove.IsChecked == true) 141 | worker.CfgAutoStart = AutoStart.REMOVE; 142 | } 143 | 144 | private void radio_ClickConfig(object sender, RoutedEventArgs e) 145 | { 146 | if (radioConfNo.IsChecked == true) 147 | worker.CfgConfigFile = ConfigFile.NOCHANGE; 148 | else if (radioConfString.IsChecked == true) 149 | worker.CfgConfigFile = ConfigFile.STRING; 150 | else if (radioConfNumOffset.IsChecked == true) 151 | worker.CfgConfigFile = ConfigFile.NUMOFFSET; 152 | else if (radioConfNumLvar.IsChecked == true) 153 | worker.CfgConfigFile = ConfigFile.NUMLVAR; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Installer/InstallerWorker.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Windows; 5 | 6 | namespace Installer 7 | { 8 | public class InstallerWorker 9 | { 10 | private Queue messageList = null; 11 | 12 | public bool IsRunning { get; private set; } = false; 13 | public bool HasError { get; private set; } = false; 14 | 15 | public bool CfgDesktopLink { get; set; } = false; 16 | public AutoStart CfgAutoStart { get; set; } = AutoStart.NONE; 17 | public ConfigFile CfgConfigFile { get; set; } = ConfigFile.NOCHANGE; 18 | 19 | public InstallerWorker(Queue messageList) 20 | { 21 | this.messageList = messageList; 22 | } 23 | 24 | public void Run() 25 | { 26 | IsRunning = true; 27 | 28 | messageList.Enqueue("Checking FSUIPC Version/State ..."); 29 | if (!InstallerFunctions.CheckFSUIPC()) 30 | messageList.Enqueue($"WARNING: FSUIPC not installed our outdated! Minimum Version for Offset Modes: {Parameters.ipcVersion}"); 31 | 32 | if (!HasError) 33 | InstallDotNet(); 34 | if (!HasError) 35 | InstallWasm(); 36 | if (!HasError) 37 | InstallApp(); 38 | if (!HasError) 39 | SetupAutoStart(); 40 | 41 | messageList.Enqueue("\nDone."); 42 | if (!HasError) 43 | messageList.Enqueue($"FenixQuartz was installed to {Parameters.appDir}"); 44 | IsRunning = false; 45 | } 46 | 47 | protected void InstallDotNet() 48 | { 49 | messageList.Enqueue("\nChecking .NET 7 Desktop Runtime ..."); 50 | 51 | if (InstallerFunctions.CheckDotNet()) 52 | messageList.Enqueue("Runtime already installed!"); 53 | else 54 | { 55 | messageList.Enqueue("Runtime not installed or outdated!"); 56 | messageList.Enqueue("Downloading .NET Desktop Runtime ..."); 57 | if (!InstallerFunctions.DownloadFile(Parameters.netUrl, Parameters.netUrlFile)) 58 | { 59 | HasError = true; 60 | messageList.Enqueue("Could not download .NET Runtime!"); 61 | return; 62 | } 63 | messageList.Enqueue("Installing .NET Desktop Runtime ..."); 64 | InstallerFunctions.RunCommand($"{Parameters.netUrlFile} /install /quiet /norestart"); 65 | File.Delete(Parameters.netUrlFile); 66 | } 67 | } 68 | 69 | protected void InstallWasm() 70 | { 71 | messageList.Enqueue("\nChecking MobiFlight WASM/Event Module ..."); 72 | 73 | if (!InstallerFunctions.CheckInstalledMSFS(out string packagePath)) 74 | { 75 | HasError = true; 76 | messageList.Enqueue("Could not determine Package Path!"); 77 | return; 78 | } 79 | 80 | 81 | if (InstallerFunctions.CheckPackageVersion(packagePath, Parameters.wasmMobiName, Parameters.wasmMobiVersion)) 82 | { 83 | messageList.Enqueue("Module already installed!"); 84 | } 85 | else 86 | { 87 | if (!InstallerFunctions.GetProcessRunning("FlightSimulator")) 88 | { 89 | messageList.Enqueue("Module not installed or outdated!"); 90 | if (Directory.Exists(packagePath + @"\" + Parameters.wasmMobiName)) 91 | { 92 | messageList.Enqueue("Deleting old Version ..."); 93 | Directory.Delete(packagePath + @"\" + Parameters.wasmMobiName, true); 94 | } 95 | messageList.Enqueue("Downloading MobiFlight Module ..."); 96 | if (!InstallerFunctions.DownloadFile(Parameters.wasmUrl, Parameters.wasmUrlFile)) 97 | { 98 | HasError = true; 99 | messageList.Enqueue("Could not download MobiFlight Module!"); 100 | return; 101 | } 102 | messageList.Enqueue("Extracting new Version ..."); 103 | if (!InstallerFunctions.ExtractZip(packagePath, Parameters.wasmUrlFile)) 104 | { 105 | HasError = true; 106 | messageList.Enqueue("Error while extracting MobiFlight Module!"); 107 | return; 108 | } 109 | File.Delete(Parameters.wasmUrlFile); 110 | } 111 | else 112 | { 113 | HasError = true; 114 | messageList.Enqueue("Can not install/update Module while MSFS is running."); 115 | MessageBox.Show("Please stop MSFS and try again.", "MSFS is running!", MessageBoxButton.OK, MessageBoxImage.Warning); 116 | } 117 | } 118 | } 119 | 120 | protected void InstallApp() 121 | { 122 | messageList.Enqueue("\nChecking Application State ..."); 123 | 124 | if (!Directory.Exists(Parameters.appDir)) 125 | { 126 | messageList.Enqueue("Installing FenixQuartz ..."); 127 | messageList.Enqueue("Extracting Application ..."); 128 | if (!InstallerFunctions.ExtractZip()) 129 | { 130 | HasError = true; 131 | messageList.Enqueue("Error while extracting Application!"); 132 | return; 133 | } 134 | } 135 | else 136 | { 137 | messageList.Enqueue("Deleting old Version ..."); 138 | if (Directory.Exists(Parameters.binDir)) 139 | Directory.Delete(Parameters.binDir, true); 140 | Directory.CreateDirectory(Parameters.binDir); 141 | messageList.Enqueue("Extracting new Version ..."); 142 | if (!InstallerFunctions.ExtractZip()) 143 | { 144 | HasError = true; 145 | messageList.Enqueue("Error while extracting Application!"); 146 | return; 147 | } 148 | } 149 | 150 | string confFile = "Installer.FenixQuartz.config.string"; 151 | if (CfgConfigFile == ConfigFile.NUMOFFSET) 152 | confFile = "Installer.FenixQuartz.config.numoffset"; 153 | else if (CfgConfigFile == ConfigFile.NUMLVAR) 154 | confFile = "Installer.FenixQuartz.config.numlvar"; 155 | 156 | if (!File.Exists(Parameters.confFile) || CfgConfigFile > 0) 157 | { 158 | messageList.Enqueue($"Creating Config File ({CfgConfigFile}) ..."); 159 | using (var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(confFile)) 160 | { 161 | using (var file = new FileStream(Parameters.confFile, FileMode.Create, FileAccess.Write)) 162 | { 163 | resource.CopyTo(file); 164 | } 165 | } 166 | } 167 | 168 | if (CfgDesktopLink) 169 | { 170 | messageList.Enqueue("Placing Shortcut ..."); 171 | InstallerFunctions.PlaceDesktopLink(); 172 | } 173 | } 174 | 175 | protected void SetupAutoStart() 176 | { 177 | if (CfgAutoStart == AutoStart.NONE) 178 | return; 179 | 180 | if (CfgAutoStart == AutoStart.FSUIPC) 181 | { 182 | messageList.Enqueue("Check/Remove MSFS Auto-Start ..."); 183 | InstallerFunctions.AutoStartExe(true); 184 | messageList.Enqueue("Setup FSUIPC Auto-Start ..."); 185 | if (InstallerFunctions.AutoStartFsuipc()) 186 | messageList.Enqueue("Auto-Start added successfully!"); 187 | else 188 | { 189 | messageList.Enqueue("Failed to add Auto-Start!"); 190 | HasError = true; 191 | } 192 | } 193 | 194 | if (CfgAutoStart == AutoStart.EXE) 195 | { 196 | messageList.Enqueue("Check/Remove FSUIPC Auto-Start ..."); 197 | InstallerFunctions.AutoStartFsuipc(true); 198 | messageList.Enqueue("Setup EXE.xml Auto-Start ..."); 199 | if (InstallerFunctions.AutoStartExe()) 200 | messageList.Enqueue("Auto-Start added successfully!"); 201 | else 202 | { 203 | messageList.Enqueue("Failed to add Auto-Start!"); 204 | HasError = true; 205 | } 206 | } 207 | 208 | if (CfgAutoStart == AutoStart.REMOVE) 209 | { 210 | messageList.Enqueue("Check/Remove FSUIPC Auto-Start ..."); 211 | InstallerFunctions.AutoStartFsuipc(true); 212 | messageList.Enqueue("Check/Remove MSFS Auto-Start ..."); 213 | InstallerFunctions.AutoStartExe(true); 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Installer/Parameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace Installer 5 | { 6 | public static class Parameters 7 | { 8 | public static readonly string fileName = "AppPackage.zip"; 9 | public static readonly string appName = "FenixQuartz"; 10 | public static readonly string appBinary = $"{appName}.exe"; 11 | public static readonly string appDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\FenixQuartz"; 12 | public static readonly string binDir = appDir + @"\bin"; 13 | public static readonly string binPath = binDir + @"\FenixQuartz.exe"; 14 | public static readonly string confFile = appDir + @"\FenixQuartz.config"; 15 | 16 | public static readonly Regex netDesktop = new Regex(@"Microsoft.WindowsDesktop.App ((\d+)\.(\d+)\.(\d+)).+", RegexOptions.Compiled); 17 | 18 | public static readonly int netMajor = 7; 19 | public static readonly int netMinor = 0; 20 | public static readonly int netPatch = 20; 21 | public static readonly string netVersion = $"{netMajor}.{netMinor}.{netPatch}"; 22 | public static readonly string netUrl = "https://download.visualstudio.microsoft.com/download/pr/08bbfe8f-812d-479f-803b-23ea0bffce47/c320e4b037f3e92ab7ea92c3d7ea3ca1/windowsdesktop-runtime-7.0.20-win-x64.exe"; 23 | public static readonly string netUrlFile = "windowsdesktop-runtime-7.0.20-win-x64.exe"; 24 | 25 | public static readonly string ipcRegPath = @"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\FSUIPC7"; 26 | public static readonly string ipcRegInstallDirValue = "InstallDir"; 27 | public static readonly string ipcRegValue = "DisplayVersion"; 28 | public static readonly string ipcVersion = "7.4.16"; 29 | 30 | public static readonly Regex wasmRegex = new Regex("^\\s*\"package_version\":\\s*\"([0-9\\.]+)\"\\s*,\\s*$", RegexOptions.Compiled); 31 | public static readonly string wasmMobiName = "mobiflight-event-module"; 32 | public static readonly string wasmMobiVersion = "1.0.1"; 33 | public static readonly string wasmUrl = "https://github.com/MobiFlight/MobiFlight-WASM-Module/releases/download/1.0.1/mobiflight-event-module.1.0.1.zip"; 34 | public static readonly string wasmUrlFile = "mobiflight-event-module.1.0.1.zip"; 35 | 36 | public static readonly string msConfigStore = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Packages\Microsoft.FlightSimulator_8wekyb3d8bbwe\LocalCache\UserCfg.opt"; 37 | public static readonly string msConfigSteam = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\Microsoft Flight Simulator\UserCfg.opt"; 38 | public static readonly string msStringPackage = "InstalledPackagesPath "; 39 | public static readonly string msExeStore = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Packages\Microsoft.FlightSimulator_8wekyb3d8bbwe\LocalCache\EXE.xml"; 40 | public static readonly string msExeSteam = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\Microsoft Flight Simulator\EXE.xml"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Installer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("FenixQuartz Installer")] 9 | [assembly: AssemblyDescription("Installer App for FenixQuartz")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Fragtality")] 12 | [assembly: AssemblyProduct("FenixQuartz Installer")] 13 | [assembly: AssemblyCopyright("Copyright © 2024")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly 18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | //Um mit dem Erstellen lokalisierbarer Anwendungen zu beginnen, legen Sie 23 | //ImCodeVerwendeteKultur in der .csproj-Datei 24 | //in einer fest. Wenn Sie in den Quelldateien beispielsweise Deutsch 25 | //(Deutschland) verwenden, legen Sie auf \"de-DE\" fest. Heben Sie dann die Auskommentierung 26 | //des nachstehenden NeutralResourceLanguage-Attributs auf. Aktualisieren Sie "en-US" in der nachstehenden Zeile, 27 | //sodass es mit der UICulture-Einstellung in der Projektdatei übereinstimmt. 28 | 29 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 30 | 31 | 32 | [assembly: ThemeInfo( 33 | ResourceDictionaryLocation.None, //Speicherort der designspezifischen Ressourcenwörterbücher 34 | //(wird verwendet, wenn eine Ressource auf der Seite nicht gefunden wird, 35 | // oder in den Anwendungsressourcen-Wörterbüchern nicht gefunden werden kann.) 36 | ResourceDictionaryLocation.SourceAssembly //Speicherort des generischen Ressourcenwörterbuchs 37 | //(wird verwendet, wenn eine Ressource auf der Seite nicht gefunden wird, 38 | // designspezifischen Ressourcenwörterbuch nicht gefunden werden kann.) 39 | )] 40 | 41 | 42 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 43 | // 44 | // Hauptversion 45 | // Nebenversion 46 | // Buildnummer 47 | // Revision 48 | // 49 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, 50 | // indem Sie "*" wie unten gezeigt eingeben: 51 | // [assembly: AssemblyVersion("1.0.*")] 52 | [assembly: AssemblyVersion("1.8.0.0")] 53 | [assembly: AssemblyFileVersion("1.8.0.0")] 54 | -------------------------------------------------------------------------------- /Installer/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Dieser Code wurde von einem Tool generiert. 4 | // Laufzeitversion: 4.0.30319.42000 5 | // 6 | // Änderungen an dieser Datei können fehlerhaftes Verhalten verursachen und gehen verloren, wenn 7 | // der Code erneut generiert wird. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Installer.Properties 12 | { 13 | 14 | 15 | /// 16 | /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. 17 | /// 18 | // Diese Klasse wurde von der StronglyTypedResourceBuilder-Klasse 19 | // über ein Tool wie ResGen oder Visual Studio automatisch generiert. 20 | // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen 21 | // mit der Option /str erneut aus, oder erstellen Sie Ihr VS-Projekt neu. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Installer.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle 56 | /// Ressourcenlookups, die diese stark typisierte Ressourcenklasse verwenden. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Installer/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Installer/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Installer.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Installer/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Installer/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Installer/quartz.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fragtality/FenixQuartz/f13c6d696e2c5809f41a8c43ff43c80097cce218/Installer/quartz.ico -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Fragtality 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FenixQuartz 2 |
3 | This Tools reads the native Fenix L-Vars to compose the content of certain Quartz Displays in the Cockpit. The Display Contents are exported as FSUIPC Offsets.
4 | Currently compatible with Fenix **2.2.0.232** upwards and MSFS **SU15**.

5 | Following Display-Values are available: 6 | - FCU Speed, Heading, Altitude and VS/FPA (Values and Is-Dashed State) 7 | - XPDR (live Input) 8 | - RMP1 and RMP2 Active & Standby Frequency (fully "live" and change correctly when "SEL" is active or ADF/HF/ILS Mode is on) 9 | - ISIS Barometer (only Digital - sorry I don't like old Stuff :grin:) 10 | - Captain Barometer 11 | - BAT1 and BAT2 12 | - Rudder Trim 13 | - Clock CHR, UTC and ET 14 | 15 |


16 | 17 | # Requirements 18 | 19 | - Windows 10/11, MSFS, Fenix :wink: 20 | - Depending on the Mode you use: [FSUIPC7](http://fsuipc.com/) 21 | - Capability to actually read the Readme up until and beyond this Point :stuck_out_tongue_winking_eye: 22 | - The Installer will install the following Software: 23 | - .NET 7 Desktop Runtime (x64) 24 | - MobiFlight Event/WASM Module 25 | 26 | [Download here](https://github.com/Fragtality/FenixQuartz/releases/latest) 27 | (Under Assests, the FenixQuartz-Installer-vXYZ.exe File) 28 | 29 |


30 | 31 | # Installation / Update / Remove 32 | Basically: Just run the Installer - it will extract it for you to a fixed Location and will also install/update the neccessary Software to your PC/Sim. It even setups Auto-Start and creates a Link on the Desktop, if you want.

33 | You can choose directly in the Installer to extract a preconfigured Configuration File for each of these Modes during Installation/Update. First-Time Installations will default to String/Offset Mode if "Do not change" is selected. Choosing any other Option for Updating/Reinstalling will overwrite the *whole* existing Configuration File. 34 | 35 | 36 | Some Notes: 37 | 38 | - FenixQuartz has to be stopped manually before installing. 39 | - If the MobiFlight Module is not installed or outdated, MSFS also has to be stopped. 40 | - The FSUIPC Version is only checked - you still have to install/update it manually. FSUIPC is only needed if you plan to use the String/Offset or Raw-Value/Offset Mode!
(Ignore the Warning in the Installer Output if you're going to use Raw-Value/L-Var Mode) 41 | - If you upgrade from Version 1.1 or below, delete your old Installation manually (it is no longer needed). 42 | - From Version 1.2 onwards, your Configuration will not be resetted after Updating 43 | - The Installation-Location is fixed to %appdata%\FenixQuartz (your Users AppData\Roaming Folder) and can not be changed. 44 | - For Auto-Start either your FSUIPC7.ini or EXE.xml (MSFS) is modified. The Installer does not create a Backup (not deemed neccessary), so if you want a Backup, do so yourself. 45 | - Do not run the Installer as Admin! 46 | - It may be blocked by Windows Security or your AV-Scanner, try if unblocking and/or setting an Exception helps (for the whole FenixQuartz Folder) 47 | 48 |


49 | 50 | # Usage 51 | You can also start FenixQuartz manually or by other means if you did not let the Installer configure an automatic Start. Either start it before MSFS or start when MSFS has finished loading and is in the Main Menu. It can safely be run with other Planes, it won't do anything besides checking every 15s if the Fenix-Binaries are running and then sleeps again. :wink:
52 | **NOTE**: The Tool must be run with the **same Elevation/User** as MSFS and Fenix! If you for whatever Reason run them "as Admin", make sure to start that Tool also as Admin! It is not needed for the Tool itself, it runs just fine if everything is started with you normal User.

53 | 54 | It does not open a Window when started, but you should see it in the System-Tray/Notification Area once it runs (a little "Q"). It is designed to run silently in the Background. It will stop itself when you exit MSFS.
55 | When you right click on that Icon you have the option to manually close it or to force a Memory Scan manually.
56 | When you left click on the Icon it will open its "Debug UI" displaying all Values as they are read from Memory. It is only really there to verify/troubleshoot found the correct Memory Locations (and uses valid Values).

57 | 58 | When you use it for Quartz Displays you don't have to start a Memory-Scan manually - just start FenixQuartz and load up the Fenix and it will output the Display-Values to FSUIPC Offset or L-Vars (depending on the Configuration).

59 | 60 |


61 | 62 | # Usage with other 3rd Party Tools 63 | FenixQuartz normally exports the Display-Values as formatted Strings for drawing them directly on the StreamDeck (the "*String/Offset Mode*"). The Output is directly "ready to use" since the Logic is already applied (displaying "----" instead of the Value or adding leading Zeros for example). You can customize that Output for your Software/Hardware with the **altScaleDelim**, **addFcuMode** or **ooMode** Options in the [Configuration](#configuration) File.

64 | 65 | **NOTE**: The Raw-Value Modes are now considered deprecated! They will be removed in the future Releases. Most Data can now be directly accessed via native Fenix L-Vars, so it is now only copying the Value from L-Var "A" to "B" mostly. 66 | 67 | Regardless of Mode or Output: The Binary will create a File called **Assignments.txt** (located in %appdata%\FenixQuartz) when executed which will tell you the Name/Location of the Values (even when no Sim is running). Please note that this is in Accordance to the current Configuration, so you only see the Name/Location for the currently configured Mode and Output! 68 | 69 |


70 | 71 | # Configuration 72 | The Path Configuration File is located at `%appdata\FenixQuartz\FenixQuartz.config` and can be edited with any Text-Editor. Starting with Version 1.2 your current Configuration is preserved when updating.
73 | The available Parameters are: 74 | - **waitForConnect**: When *true*, the Binary will wait until the Sim is running and connected. Default: *"true"* 75 | - **offsetBase**: The first (FSUIPC) Offset Address to use (hexadecimal). Default: *"0x5408"* 76 | - **rawValues**: When *true*, the FCU Values are exported directly as numeric Values ("Raw-Value/Offset" Mode). Default: *"false"* 77 | - **useLvars**: When *true*, the Values are exported as L-Vars instead of FSUIPC Offsets ("Raw-Value/L-Var" Mode). Only works when rawValues is also set!. Default: *"false"* 78 | - **updateIntervall**: The time between each Update in Milliseconds (Read Memory-Locations -> Write to Variables). Default: *"100"* 79 | - **scaleMachValue**: The MACH Value is scaled to a float Value in in both Raw-Value Modes (e.g. it is output as 0.79 instead of 79) 80 | - **altScaleDelim**: The Character to be inserted in the FCU Altitude String when the Scale is set to 100. Has no Effect on any the Raw-Value Mode. Default: *" "* (Space) 81 | - **addFcuMode**: If *false*, the FCUs Displays show only the Value (no SPD/HDG/VS Header) in one Line. Has no Effect on any Raw-Value Mode. Default: *"true"* 82 | - **ooMode**: If *true*, the Zeroes on the VS-Display will be exported as "o" instead of "0" if your Hardware/Font allows Letters to be displayed (only relevant in String/Offset Mode). Default *"false"* 83 | - **lvarPrefix**: The Prefix of the L-Var Names used for export. Still defaults to the old Name FNX2PLD, but you can of course change that! The old Name is used to ensure Compatibility. Default *"FNX2PLD_"* 84 | -------------------------------------------------------------------------------- /Restart-FenixQuartz.ps1: -------------------------------------------------------------------------------- 1 | $destDir = "X:\Full\Path" 2 | Stop-Process -Name FenixQuartz -ErrorAction SilentlyContinue 3 | Sleep(3) 4 | Start-Process -FilePath ($destDir + "\FenixQuartz.exe") -WorkingDirectory $destDir -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fragtality/FenixQuartz/f13c6d696e2c5809f41a8c43ff43c80097cce218/img/icon.png --------------------------------------------------------------------------------