Integration Patterns & Decision Records
Internal Analysis Document
Note: This document captures WHY architectural decisions were made. Used to enhance
architecture.mdand inform new documentation.
1. Package Installation Flow & Quirks
Decision: Multi-Phase Install Process
Why this approach?
Phase 1: Analyze package contents
↓
Phase 2: Identify dependencies (regex matching)
↓
Phase 3: Download all files (with cache check)
↓
Phase 4: Upload to Xbox (sequential: main + deps)
↓
Phase 5: Signal package manager to install
Rationale:
- Xbox package manager limitations: Requires all files (main + dependencies) uploaded before installation request
- Network efficiency: Pre-analysis avoids re-downloading if dependencies already cached
- User feedback: Multi-phase allows progress reporting at each step
Package Identification Patterns
Dependency Regex (PackageInstallService.cs:23-24):
(?i)(microsoft\.|vclibs|net\.core|ui\.xaml|net\.native|vcruntime|dotnet|runtime\.)
Why this pattern?
- Xbox packages often name Microsoft frameworks/runtimes with these prefixes
- Case-insensitive (
(?i)) because package naming varies - Regex matching faster than directory scanning
Junk Filter Regex (lines 26-29):
(?i)(\.cer$|\.pfx$|add-appdevpackage|install\.ps1|\.appxsym$|\.psd1$|
telemetrydependenc|logsideloading|diagnostics\.tracing|...)
Why filter these?
.cer,.pfx: Certificates/keys—harmful if installed as packagesinstall.ps1: PowerShell scripts from developer machinestelemetrydependenc,logsideloading: Diagnostic packages unnecessary on Xbox- Prevents installation errors and cluttered package list
Dependency Folder Detection (lines 20-21):
private static readonly HashSet<string> DepFolderNames = new(
StringComparer.OrdinalIgnoreCase) { "Dependencies", "deps", "dep" };
Why case-insensitive matching?
- Different package creators use different conventions
- Case-insensitive HashSet handles both
Dependencies/anddependencies/
Xbox Package Manager Polling Quirk
Pattern in XboxDeviceService.cs:571-590:
private async Task WaitForPackageManagerReady()
{
// Poll until package manager ready
for (int i = 0; i < 15; i++)
{
await Task.Delay(i < 3 ? 2000 : 3000);
var info = await GetPackageManagerInfoAsync();
if (info?.IsReady == true)
return;
}
}
Why polling with backoff?
- Xbox package manager is a background service that can be busy
- Uploading one file blocks the manager briefly
- Exponential backoff (2s for first 3 attempts, 3s after) balances responsiveness + tolerance
- 15 attempts * 3s = 45s max wait
Real-world observation:
- Initial attempts fail quickly (manager still processing)
- Later attempts succeed more often
- Backoff prevents hammering the API
2. SFTP & External Media Access
Decision: SSH.NET Wrapper with Connection Pooling
Why not direct SSH?
- SSH.NET (Renci.SshNet) provides:
- Both SSH and SFTP protocols
- Connection management
- Built-in timeouts and keep-alive
- Exception handling consistency
SftpService Connection Lifecycle
Key decision: Async wrapper around sync SSH.NET library (SftpService.cs:29-62)
public async Task ConnectAsync(string host, int port, string user, string pass)
{
await Task.Run(() => {
_ssh = new SshClient(connInfo);
_ssh.Connect(); // Synchronous, can block
_sftp = new SftpClient(connInfo);
_sftp.OperationTimeout = TimeSpan.FromSeconds(15);
_sftp.KeepAliveInterval = TimeSpan.FromSeconds(30);
_sftp.Connect();
});
}
Why?
- SSH.NET is synchronous—can’t use
async/awaitdirectly Task.Run()ensures connection doesn’t block UI thread- OperationTimeout (15s) prevents hanging on slow Xbox network
- KeepAliveInterval (30s) prevents connection dropout during file transfers
Path Normalization
Pattern: Convert Windows → Unix paths
private static string NormalizePath(string path)
{
path = path.Replace('\\', '/'); // Windows → Unix
if (path.Length >= 2 && path[1] == ':' && !path.StartsWith('/'))
path = "/" + path; // C:\ → /C:\
return path;
}
Why?
- Xbox filesystem is Linux-based (Xenon OS)
- Windows paths use backslash; Unix uses forward slash
- Drive letters (C:) need slash prefix for Xbox SFTP
Credentials from Xbox Dev Portal
Pattern: Fetch SMB password via HTTP, use for SSH (XboxDeviceService.cs:90-105)
public async Task<string?> FetchSmbPasswordAsync()
{
var response = await _http.GetAsync("/ext/smb/developerfolder");
// Response contains: { "Password": "Dev**" }
// Use this password for SSH auth as well
}
public SshConnectionInfo GetSshCredentials()
{
return new SshConnectionInfo(uri.Host, 22, "DevToolsUser", _smbPassword ?? _password);
}
Why?
- Xbox Dev Mode has a single credential: the Device Portal password
- SMB folder access (USB drives) uses same password as SSH
- Fetch SMB password from Dev Portal HTTP API
- Fall back to Device Portal password if SMB password not available
- Reuse credentials for SSH (port 22, user “DevToolsUser”)
3. WebSocket Performance Streaming
Decision: Real-Time Performance Metrics via WebSocket
Why WebSocket instead of polling HTTP?
- Real-time performance data (CPU, GPU, memory per core)
- HTTP polling would require sampling 10+ times/second
- WebSocket provides server push—more efficient
- Xbox Device Portal expects WebSocket for performance data
WebSocket Endpoint & Frame Structure
Endpoint: wss://{xbox-ip}:11443/api/resourcemanager/processes
Frame contents (PerformanceSnapshot model):
- CPU usage per core
- GPU clock frequency
- Memory usage (free/used/total)
- Temperature readings (each core)
- Timestamp
Connection Lifecycle
Pattern in PerformanceViewModel (document/code not shown, TBD from agent):
- Connect on window open
- Receive JSON frames continuously
- Parse → update ObservableProperty
- Disconnect on window close
- Issue: CancellationTokenSource never disposed (tech debt #13)
4. Xbox Device Portal Authentication
Decision: HTTP Basic Auth + CSRF Token via Cookie
Why Basic Auth?
- Xbox Device Portal uses HTTP Basic for simplicity
- HTTP only (no OAuth), so Basic Auth is standard
- Credentials:
Authorization: Basic base64(username:password)
CSRF Token Handling
Pattern in XboxDeviceService:
- First request gets 401 Unauthorized
- Response includes
CSRF-Tokenheader - Store in CookieContainer
- Subsequent requests include token
Why CSRF?
- Xbox Device Portal protects against Cross-Site Request Forgery
- Token validated on state-changing operations (POST, DELETE)
Certificate Validation Bypass
Code: XboxDeviceService.cs:32-33
ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
Why bypass certificate validation?
- Xbox console uses self-signed certificates
- Dev Mode is not for production use
- Certificate pinning would be too strict
- This is acceptable for a developer tool
5. Catalog & Cache Strategy
Decision: 6-Hour TTL Cache with Stale Fallback
Why 6 hours?
- Emulation Revival catalog updates infrequently
- 6 hours balances freshness with network efficiency
- Users unlikely to install the same package twice in 6 hours
Why persistent disk cache?
- Allows offline browsing after first catalog fetch
- Cached in:
%APPDATA%\XBVault\cache\catalog-api.json - Survives app restart
Fallback Strategy: Stale Cache on API Failure
Pattern in CatalogApiService.cs:64-80:
// Try fresh data from API
var items = await TryFetchJsonApiAsync(progress);
if (items is not null && items.Count > 0)
return items;
// API failed — use stale cache (ignore TTL)
Logger.Warn("JSON API failed, trying stale cache");
var stale = LoadFromCache(ignoreTtl: true);
Why this fallback?
- Emulation Revival API can have brief downtime
- Users still need to browse existing packages
- Stale data is better than no data
- Prevents “catalog unavailable” errors
6. Settings Persistence: XOR+Base64, NOT Encryption
Decision: Obfuscation Only, Not Encryption
Code in CryptoService:
// Pattern: XOR each byte with key, then Base64 encode
public string Encrypt(string plaintext, string key)
{
var key_bytes = Encoding.UTF8.GetBytes(key);
var plain_bytes = Encoding.UTF8.GetBytes(plaintext);
for (int i = 0; i < plain_bytes.Length; i++)
plain_bytes[i] ^= key_bytes[i % key_bytes.Length];
return Convert.ToBase64String(plain_bytes);
}
Why NOT full encryption?
- Passwords stored in
%APPDATA%/XBVault/settings.json - Full encryption would require secure key storage (complex)
- XOR+Base64 obfuscates credentials from casual inspection
- If attacker has file system access, they have the key too (hardcoded in assembly)
Design choice rationale:
- Goal: Prevent passwords from visible in JSON
- Threat model: Accidental file inspection, not targeted attack
- Trade-off: Simplicity vs cryptographic security
- Acceptable for: Developer tool, not banking app
Existing settings in file:
{
"Xbox": {
"BaseUrl": "https://192.168.1.100:11443",
"Username": "devuser",
"Password": "VGVzdFBhc3N3b3Jk" // Base64 of XORed bytes
}
}
7. USB Drive Permission Wizard (Windows Only)
Decision: Use WMI + icacls for Permissions
Why WMI?
- Windows Management Instrumentation gives access to USB drive list
System.Managementnamespace provides WMI interface- Only way to enumerate USB devices on Windows without elevated privileges initially
Why icacls?
- Command-line tool to modify NTFS permissions
- “ALL APPLICATION PACKAGES” SID allows Xbox Dev Mode to read USB
- Xbox runs as SYSTEM with restricted sandbox
- Need explicit permission grant
Pattern in UsbPermissionViewModel:
- List USB drives via WMI
- For each drive:
icacls E: /grant "*S-1-15-2-1:(OI)(CI)F" /T /C - Translation: Grant ALL APPLICATION PACKAGES full permissions recursively
Why not just Run As Admin?
- UsbPermissionWindow wants to avoid elevation if possible
- Gracefully handles non-elevated mode
- Only elevates if user clicks “Grant Permissions”
Windows-specific quirk:
// In UsbDriveDetector.cs
#if WINDOWS
// WMI code here
#endif
- Prevents Linux/macOS build errors
- Linux/macOS don’t have WMI or Xbox dev mode support
8. Async Patterns & ConfigureAwait Strategy
Current Approach (NOT using ConfigureAwait)
Services layer:
public async Task<bool> InstallPackageAsync(...)
{
var response = await _http.PostAsync(...); // No ConfigureAwait
return response.IsSuccessStatusCode;
}
ViewModels:
[RelayCommand]
private async Task BrowseItemAsync()
{
var catalog = await _catalogService.FetchCatalogAsync(); // No ConfigureAwait
Items.Clear();
Items.AddRange(catalog); // UI update
}
Why NOT using ConfigureAwait(false)?
Current decision (implicit):
- Development prioritized over performance
- Desktop app (not server), so context switching overhead minimal
- All ViewModels need UI thread for ObservableProperty updates anyway
- Consistency: don’t mix ConfigureAwait(true) and ConfigureAwait(false)
Potential Improvement
Recommended approach (future):
// Services: use ConfigureAwait(false)
private async Task<string?> FetchDataAsync()
{
var response = await _http.GetAsync(...).ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
// ViewModels: no ConfigureAwait (stays on UI thread)
private async Task LoadDataAsync()
{
var data = await _service.FetchDataAsync();
Items.Clear();
Items.AddRange(data); // Must be on UI thread
}
Tech debt: Item #4 in TECH-DEBT.md
9. Manual Service Composition (No DI Container)
Current Pattern in App.axaml.cs
var xboxService = new XboxDeviceService();
var cacheService = new CacheService();
var installService = new PackageInstallService(cacheService, xboxService);
Why not use Microsoft.Extensions.DependencyInjection?
Trade-offs:
- Pro: Explicit composition, easy to follow for small projects
- Con: Manual wiring becomes tedious as services grow (currently ~12 services)
- Decision: App is small enough to manage manually
Potential Issue: CatalogApiService
Pattern in BrowseViewModel.cs:40:
public BrowseViewModel(...)
{
_catalogService = new CatalogApiService(); // Created inline!
}
Problem:
- Creates instance per ViewModel, not reused
- Multiple CatalogApiService instances = multiple HTTP clients
- Cache not shared across instances if multiple VMs created
Tech debt: Item #12 in TECH-DEBT.md
10. Window Template Pattern
All Dialogs Share Same Pattern
AXAML Structure (WindowDecorations=”None”):
<Window WindowDecorations="None"
Background="{StaticResource SurfaceBrush}"
Width="500" Height="300">
<Border BorderBrush="#447F3E" BorderThickness="2" Margin="1">
<Grid RowDefinitions="auto,*">
<!-- Title Bar with gradient -->
<Border Background="{StaticResource TitleGradient}"
PointerPressed="OnTitleBarPointerPressed"
CornerRadius="8,8,0,0">
<!-- Close button, title -->
</Border>
<!-- Content -->
<ScrollViewer Grid.Row="1">
<!-- Window content -->
</ScrollViewer>
</Grid>
</Border>
</Window>
Code-behind pattern:
private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
{
BeginMoveDrag(e); // Allow drag to move window
}
private void OnCloseClick(object? sender, RoutedEventArgs e)
{
Close();
}
Why This Pattern?
- Xbox UI Style: Blades theme (360 inspired) uses green accents
- No OS Chrome: WindowDecorations=”None” removes Windows title bar
- Green Border: #447F3E (Xbox green)
- Custom Close Button: Control behavior, ensure consistency
- Draggable Title Bar: Custom drag via BeginMoveDrag()
Known Avalonia Issue: Image Clipping
Quirk: Border CornerRadius doesn’t clip Image content
Pattern in BrowseView.axaml:
<!-- This doesn't clip image corners! -->
<Border CornerRadius="8,8,0,0">
<Image Stretch="UniformToFill" ... />
</Border>
Workaround (not yet implemented):
- Apply
Clipgeometry via code-behind - Or use
ImageBrushinside Border - Avalonia issue: ClipToBounds doesn’t work on Borders with Images
Tech debt: Item #8 in TECH-DEBT.md
Summary: Integration Decisions
| Decision | Rationale | Trade-off |
|---|---|---|
| Multi-phase package install | Xbox limitations + network efficiency | Complexity |
| SFTP over SSH.NET | Connection management + reliability | Dependency |
| WebSocket for performance | Real-time streaming efficiency | Complexity |
| Basic Auth + CSRF | Xbox API standard | Manual token handling |
| 6-hour cache + stale fallback | Offline support + freshness balance | Sync inconsistency |
| XOR+Base64 obfuscation | Simplicity vs security | Not cryptographically secure |
| WMI + icacls for USB | Windows-only access to drives | Platform-specific code |
| Manual service composition | Simplicity for current size | Will need DI at scale |
| Window template pattern | Blades theme consistency | Duplication across windows |
Document version: 1.0
Last updated: 2026-06-25
Status: Complete