Skip to content

Architecture

Bubbleshum edited this page May 22, 2026 · 1 revision

Architecture

WPR runs a Windows Phone game's original assemblies, IL-rewritten at install time to redirect WP / Silverlight / XNA API calls to in-tree shims. This page covers the pipeline, the shim project layout, and the reinstall-vs-rebuild rule that trips up most contributors at least once.


Pipeline

.xap / XNA folder
      │
      ▼ LibraryScanner          discovers packages
      ▼ ApplicationInstaller    unpacks to %LocalAppData%\WPR\Apps\<ProductId>
      ▼ ApplicationPatcher      Cecil-rewrites every .dll; keeps .dll.original
      ▼ XnaAchievementSeeder    populates SQLite achievements DB
      │
      ▼ (user clicks Run)
      ▼ XnaLauncher  →  ApplicationLaunch.Start   XNA via FNA
      ▼ SilverlightLauncher.LaunchAsync            Silverlight XAPs

The pipeline runs once per .xap at install. Steps 1–4 happen the first time you click Add; thereafter the user assembly on disk has already been rewritten and a <game>.dll.original sibling sits next to it.

Per-game install layout

%LocalAppData%\WPR\
├── Apps\
│   └── <ProductId>\
│       ├── <Game>.dll            ← Cecil-rewritten user assembly
│       ├── <Game>.dll.original   ← untouched copy of the original
│       ├── AppManifest.xaml
│       ├── WMAppManifest.xml
│       ├── Content\…
│       └── wpr_game_debug.log    ← per-game debug trace (DEBUG builds)
└── Database\
    ├── achievements.db           ← shared SQLite DB across all installs
    └── applications.db

wpr_game_debug.log is the first place to look when a game silently crashes or shows a black screen — see Troubleshooting.


ApplicationPatcher — the IL rewrite

ApplicationPatcher.PatchDll walks every *.dll in the per-game install directory with Mono.Cecil and rewrites two kinds of references:

Table What it redirects
Patches Type references — e.g. System.Windows.Controls.ImageWPR.SilverlightCompability.Image, Microsoft.Xna.Framework.GamerServices.SignedInGamerWPR.Core...SignedInGamer.
MemberPatches Specific method/property references that need rerouting independent of their declaring type.

The original DLL is preserved as <name>.dll.original so the patcher can be re-run without having lost the source.


Reinstall vs. rebuild — the rule

Change Action needed
Shim implementation change
(any .cs under WPR.*Compability, Microsoft.*, System.*, FNA, GamerServices, etc.)
Rebuild only. Installed games reference the shim assembly, not a snapshot of it — next launch picks up the new behaviour.
Patcher table change
(new entries in ApplicationPatcher.cs's Patches / MemberPatches)
Rebuild and reinstall the affected games. The IL was rewritten at install time; new redirects don't apply retroactively to already-rewritten .dlls.

Common gotcha: "I added a shim type but the game still NREs." Did you also add a Patches entry? If yes — did you reinstall the game?


Shim project layout

Src/:

Project Role
Core/WPR Install / launch / patch pipeline, EF Core models, DB
Core/WPR.Common Logging, paths, configuration
Core/WPR.SilverlightCompability Silverlight 4 / WP XAML re-impl on Avalonia
Core/WPR.WindowsCompability System.Windows.* shims (Application, BitmapImage, IsolatedStorage, …)
Core/WPR.StandardCompability System.ServiceModel / WCF-lite shims
Core/WPR.XnaCompabilityPatch XNA-side shims layered on top of FNA
Core/Microsoft.Phone Microsoft.Phone.* (Shell, Tasks, Marketplace, Scheduler, …)
Core/Microsoft.Xna.Framework.GamerServices Gamer profile, achievements, leaderboards
Core/Microsoft.Device.Sensors Accelerometer / Compass
Core/System.Device System.Device.Location
UI/WPR.UI Shared Avalonia UI (views, view-models, launchers)
UI/WPR.UI.Desktop Windows entry point (net8.0-windows10.0.17763.0)
UI/WPR.UI.Android Android entry point
ThirdParty/fna FNA — XNA reimplementation, lightly forked for diagnostics
ThirdParty/Icons.Avalonia Vendored Projektanker icons, patched for Avalonia 11.3.9

File-layout convention in WPR.SilverlightCompability

This project mirrors the upstream Silverlight / Windows Phone namespace hierarchy as directories — one C# class per file, file path matches where the type lives upstream:

Upstream type File path
System.Windows.Shapes.Rectangle System/Windows/Shapes/Rectangle.cs
System.Windows.Controls.Primitives.Popup System/Windows/Controls/Primitives/Popup.cs
System.Windows.Media.Animation.Storyboard System/Windows/Media/Animation/Storyboard.cs
Microsoft.Phone.Shell.PhoneApplicationService Microsoft/Phone/Shell/PhoneApplicationService.cs

The C# namespace declaration in every file stays WPR.SilverlightCompability — the directory structure is pure organisation, the assembly is one flat DLL. The patcher target paths (NewNamespace in ApplicationPatcher.cs) refer to that flat namespace.

Keep the doc comment /// Shim for <c>System.X.Y.TypeName</c>. on each shim file — it's the canonical record of which upstream type the file shadows, and tooling can grep for it.

Files at the project root are not type shims — they're WPR-internal runtime / helper code (renderers, the pointer-to-gesture bridge, XAML helpers, hosting glue, theme constants).

WPR.WindowsCompability and WPR.XnaCompability are still flat — the mirror-tree convention has only been applied to WPR.SilverlightCompability so far.


Runtime types

ApplicationType.cs recognises three .xap flavours:

Type Status Notes
XNA Working Main path; runs on FNA via WPR.XnaCompability.
Silverlight Experimental Boots a small set of XAPs through the in-tree WPR.SilverlightCompability Avalonia re-impl.
ModernNative Not supported C++/CX + WinRT apps ship as native PE binaries — out of scope.

Achievements

Two paths populate the per-game achievements DB at install time:

  1. XnaAchievementCodeExtractor scans the install folder for known patterns:

    • Source D — hardcoded catalogue keyed by ProductId, for games that stash their achievement keys in an inline static array (e.g. PvZ's Sexy.Achievements.ACHIEVEMENT_KEYS).
    • Source A — XNA XML content catalogue (Content/xml/socialnetworks.xml.xnb).
    • Source B — IL ldstr literals near AwardAchievement* callsites.
    • Source CContent/Achievements/*.xnb filenames.

    First source that yields entries wins.

  2. TrueAchievements scrape (best-effort, used to backfill descriptions and icons when the extractor didn't recover them).

At runtime, SignedInGamer.BeginGetAchievements reads from the DB and returns the AchievementCollection the game expects.


Logging

Two output channels:

Channel Path When
Host log UI console / standard Trace listener Always
Per-game debug log %LocalAppData%\WPR\Apps\<ProductId>\wpr_game_debug.log DEBUG builds only

The per-game log is gated by the WprDebugTrace wrapper — Release builds elide the trace formatting and file listener entirely (no log spam, no per-frame cost). All [wpr-trace] / [wpr-ex] / [wpr-heartbeat] / [wpr-content] lines flow through it.


Further reading

  • CLAUDE.md in the main repo — the canonical project conventions, build-flag pitfalls, and rules-of-the-road that this page summarises.
  • Building WPR — how to compile.
  • Contributing — coding guidelines, PR workflow.

Clone this wiki locally