Personal .NET Learning Project 2026-05-22

PMDG Livery Manager

A small Windows utility I built to make PMDG livery installs in Microsoft Flight Simulator 2020 much less tedious while I was learning .NET.

.NET 10C#WPFMVVM
PMDG Livery Manager showing the Microsoft Store install type, a PMDG 737 package, and an installed Malaysia Airlines livery with thumbnail preview

Project Overview

Turning an annoying flight-sim ritual into a one-click Windows tool while learning how to build a real desktop app in .NET.

I built PMDG Livery Manager as a self project for two reasons at the same time. First, I genuinely wanted to learn .NET by making something real instead of following another tutorial. Second, I kept running into the same headache in Microsoft Flight Simulator 2020 whenever I wanted to install third-party PMDG liveries: unzip the package, inspect `livery.json`, rename `options.ini`, copy the file into PMDG's work folder, and regenerate `layout.json` again. It was repetitive enough that it stopped being a quick hobby task, so I turned it into a small Windows utility that solves a problem I actually have.

Role Solo developer
Timeline March 2026
Team / Collaboration Solo project

Key Highlights

Project type
Self project built to learn .NET by solving a real personal annoyance
UI model
Single-window WPF app using MVVM and CommunityToolkit.Mvvm
Key workflow
ZIP validation, install/uninstall, PMDG INI sync, layout regeneration
Distribution
Windows x64 self-contained publish for non-developer users

The Challenges

  • Installing PMDG liveries manually in MSFS 2020 is much more awkward than dropping a folder into Community. You have to extract the ZIP, inspect `livery.json`, rename `options.ini` to the correct ATC ID, copy it into a separate PMDG work directory, and then rebuild the package layout.
  • The path logic changes depending on whether the simulator came from Steam, Microsoft Store, or a custom install, which makes the process even more error-prone when you just want to try a repaint quickly.
  • Because I was also using this project to learn .NET, I wanted something practical enough to teach me desktop state management, async workflows, and file-system edge cases instead of another toy CRUD app.

Project Goals

  • Collapse the full livery install flow into a simple desktop experience that feels like picking a target aircraft and clicking install.
  • Make the tool safe enough for real use by detecting installed PMDG packages, validating ZIP contents against the selected aircraft, and handling both install and uninstall cleanly.
  • Use the project as a serious .NET learning exercise, including WPF, MVVM, dependency boundaries, async UI work, and Windows publishing.

System Architecture

Component 01
WPFC#MVVMCommunityToolkit.Mvvm

Desktop shell and MVVM orchestration

The app is a native Windows WPF application with one main window and one main view model. `MainViewModel` owns UI state such as selected store type, detected aircraft, installed liveries, loading state, search text, and status messaging, while the file-system-heavy behavior stays behind service interfaces instead of leaking into code-behind.

Component 02
Windows file systemPathServiceAircraftDiscoveryService

Path resolution and aircraft discovery

`PathService` resolves the Community folder and PMDG work folder for Steam, Microsoft Store, or custom installs. `AircraftDiscoveryService` then scans the Community directory for `pmdg-aircraft-*-liveries` packages and builds the list of aircraft the UI can manage.

Component 03
JSON parsingThumbnail discoveryObservable collections

Livery discovery and browsing

Once an aircraft is selected, `LiveryDiscoveryService` enumerates the installed livery folders under `SimObjects/Airplanes`, parses `livery.json` for each package, and tries several thumbnail locations so the app can present something more useful than just a raw folder list.

Component 04
ZipArchiveRegexSystem.Text.JsonLiveryPackageInspectionService

Install safety and package mutation

Before installation, `LiveryPackageInspectionService` opens the ZIP, scans supported text files, extracts the ATC ID, and tries to identify the intended PMDG aircraft through `productPackage`, regex matching, and alias tokens. That validation step is important because it reduces the chance of installing the wrong repaint into the wrong aircraft package. If validation passes, `LiveryInstallationService` extracts the ZIP, renames `options.ini` to `{atcId}.ini`, and copies that file into the PMDG work folder when available.

Component 05
LayoutServiceUTF-8 JSON outputLong-path handling

Layout and manifest regeneration

After every install or uninstall, `LayoutService` rebuilds the aircraft package `layout.json` and updates `manifest.json` total size so the livery package stays consistent with how MSFS expects it. The implementation even includes a short-path move to `C:\_LM_TEMP` plus a `\\?\` fallback because Community folder paths can get long enough to hit Windows path limits.

Component 06
Manual DIConfigServiceAppData persistence

Persistence and lightweight app composition

The app keeps its architecture intentionally simple. `App.OnStartup` wires the services manually instead of bringing in a full IoC container, and `ConfigService` stores the selected store type, custom path, and last aircraft inside `%APPDATA%/LiveryManager/config.json` so the app opens back into a useful state.

Technology Rationale

WPF on .NET 10
I wanted to learn the native .NET desktop stack directly, and this utility is Windows-only anyway because it targets the PMDG + MSFS setup most simmers use.
MVVM with CommunityToolkit.Mvvm
The toolkit keeps the view model readable with generated observable properties and relay commands, which made it easier to focus on app behavior instead of boilerplate.
Manual dependency injection
For a small utility with one main window, explicit service construction in `App.OnStartup` kept the architecture understandable without adding a DI framework just for the sake of it.
Separate inspection, installation, discovery, and layout services
Splitting the file-system logic by responsibility made the project much easier to reason about, especially once batch installs, ZIP validation, and search behavior were added.
Self-contained Windows publish
I wanted the release to be usable by people who just want the tool, not by developers willing to install SDKs and runtimes first.

Tradeoffs & Constraints

Native Windows fit vs. cross-platform reach

WPF was the right learning and UX fit for a Windows-only MSFS helper, but it also means the app is intentionally tied to one platform.

Simple architecture vs. future scale

Manual DI and a single main view model keep the project approachable, but they would need more structure if the tool grew into a larger multi-window desktop application.

Direct file manipulation vs. edge-case handling

Working directly with ZIP contents and simulator package folders makes the utility genuinely useful, but it also forces the app to care about path length limits, missing work folders, malformed metadata, and aircraft-package mismatches.

Shipped utility vs. test coverage

The project is strong as a practical tool and learning vehicle, but it currently leans more on careful service boundaries and manual verification than on automated tests.

Lessons Learned

  • Learning a framework becomes much easier when the project solves a real annoyance you keep coming back to, because every architectural choice has an immediate payoff.
  • Desktop utilities still benefit from clean boundaries; putting path logic, ZIP inspection, and layout regeneration into separate services made the app much easier to extend later.
  • Windows file-system work gets messy fast once deep simulator folders and generated package metadata are involved, so long-path handling and defensive validation are not optional details.
  • Good utility UX is mostly about removing fragile manual steps and giving clear status feedback, not about filling the screen with controls.

Outcomes & Next Steps

The result is a working WPF utility that auto-detects the simulator store path, discovers installed PMDG livery packages, shows installed liveries with thumbnail previews, supports drag-and-drop or multi-ZIP installs, validates packages before copying files, and regenerates both `layout.json` and `manifest.json` after every change. More importantly for me, it turned .NET from something I was only reading about into something I had actually used to ship a tool I personally wanted.

  • Expand compatibility and metadata handling for more PMDG aircraft variants and more livery package shapes found in the wild.
  • Improve the browsing experience further with richer livery details and better duplicate or conflict handling during install.
Back to all projects