├── .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 | This App installs/updates FenixQuartz for your current User.
12 | All Software Requirements, will be automatically checked and installed.
13 | But NOTE: For FSUIPC only the State and Version is checked, you have to install/update it manually!
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 | Remember to set/update your Anti-Virus Exclusion, if necessary.
30 | If you have installed FenixQuartz for the first Time, reboot your PC after Installation has finished!
31 | Install!
32 | Remove!
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
--------------------------------------------------------------------------------