---
phase: quick
plan: 260409-roi
type: execute
wave: 1
depends_on: []
files_modified:
  - scripts/deploy_driver.ps1
  - scripts/register_driver.ps1
  - installer/BeyondProximity.iss
autonomous: true
must_haves:
  truths:
    - "All three files detect Steam install path from Windows Registry instead of hardcoding"
    - "Users with non-standard Steam paths get correct default directory and vrpathreg resolution"
    - "Users with standard Steam paths see no behavior change (fallback to current defaults)"
  artifacts:
    - path: "scripts/deploy_driver.ps1"
      provides: "Registry-based Steam path detection for TargetPath default and vrpathreg"
      contains: "HKLM.*Valve.*Steam"
    - path: "scripts/register_driver.ps1"
      provides: "Registry-based Steam path detection for SteamVR bin path"
      contains: "HKLM.*Valve.*Steam"
    - path: "installer/BeyondProximity.iss"
      provides: "Registry-based Steam path detection in Pascal Script for DefaultDirName and vrpathreg"
      contains: "RegQueryStringValue"
  key_links:
    - from: "Registry HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam"
      to: "All three files"
      via: "InstallPath value read at script/installer startup"
      pattern: "WOW6432Node.Valve.Steam"
---

<objective>
Update installer and deployment scripts to auto-detect non-standard Steam installation
locations via Windows Registry instead of hardcoding `C:\Program Files (x86)\Steam`.

Purpose: Users who install Steam to non-default locations (e.g., `D:\Games\Steam`) currently
get broken installs because vrpathreg.exe paths and default install directories are hardcoded.

Output: Three updated files that read Steam's install path from registry with fallback to
current hardcoded defaults.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@installer/BeyondProximity.iss
@scripts/deploy_driver.ps1
@scripts/register_driver.ps1

Steam registry locations (in priority order):
1. `HKLM:\SOFTWARE\WOW6432Node\Valve\Steam` -> `InstallPath` (most reliable, e.g. `C:\Program Files (x86)\Steam`)
2. `HKCU:\SOFTWARE\Valve\Steam` -> `SteamPath` (user-level, uses forward slashes: `C:/Games/Steam`)

From the detected Steam root, standard subdirectories are:
- `steamapps\common\Bigscreen Beyond Driver` (driver install target)
- `steamapps\common\SteamVR\bin\win64\vrpathreg.exe` (driver registration tool)
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add Steam path detection to PowerShell scripts</name>
  <files>scripts/deploy_driver.ps1, scripts/register_driver.ps1</files>
  <action>
Create a shared detection pattern in both PowerShell scripts (no shared module needed -- just
duplicate the small helper since these are standalone scripts).

In BOTH scripts, add a function `Get-SteamPath` near the top (after param block) that:
1. Tries `(Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Valve\Steam' -ErrorAction SilentlyContinue).InstallPath`
2. If null, tries `(Get-ItemProperty -Path 'HKCU:\SOFTWARE\Valve\Steam' -ErrorAction SilentlyContinue).SteamPath` and replaces forward slashes with backslashes
3. If both null, returns `$null`

**deploy_driver.ps1 changes:**
- Line 4: Change default param from hardcoded path to use detected Steam path.
  Replace: `[string]$TargetPath = "C:\Program Files (x86)\Steam\steamapps\common\Bigscreen Beyond Driver"`
  With: `[string]$TargetPath = ""`
  Then after the `Get-SteamPath` function, add logic:
  ```
  if (-not $TargetPath) {
      $steamRoot = Get-SteamPath
      if ($steamRoot) {
          $TargetPath = Join-Path $steamRoot "steamapps\common\Bigscreen Beyond Driver"
      } else {
          $TargetPath = "C:\Program Files (x86)\Steam\steamapps\common\Bigscreen Beyond Driver"
      }
  }
  ```
- Line 135: Replace hardcoded vrpathreg path with:
  ```
  $vrpathreg = if ($steamRoot) {
      Join-Path $steamRoot "steamapps\common\SteamVR\bin\win64\vrpathreg.exe"
  } else {
      "C:\Program Files (x86)\Steam\steamapps\common\SteamVR\bin\win64\vrpathreg.exe"
  }
  ```
  Note: `$steamRoot` should be computed once early (from `Get-SteamPath`) and reused, not called twice.

**register_driver.ps1 changes:**
- Lines 9-10: Replace hardcoded `$steamVrBin` with:
  ```
  $steamRoot = Get-SteamPath
  if ($steamRoot) {
      $steamVrBin = Join-Path $steamRoot "steamapps\common\SteamVR\bin\win64"
  } else {
      $steamVrBin = "C:\Program Files (x86)\Steam\steamapps\common\SteamVR\bin\win64"
  }
  ```
- Also update the fallback steamvr.vrsettings path on line 48 to use `$steamRoot` if available:
  ```
  $steamVrSettings = if ($steamRoot) {
      Join-Path $steamRoot "config\steamvr.vrsettings"
  } else {
      "C:\Program Files (x86)\Steam\config\steamvr.vrsettings"
  }
  ```
  </action>
  <verify>
    <automated>powershell -NoProfile -Command "& { . scripts/deploy_driver.ps1 -TargetPath 'C:\fake' 2>&1 | Out-Null; Write-Host 'deploy_driver syntax OK' }; & { $content = Get-Content scripts/register_driver.ps1 -Raw; if ($content -match 'Get-SteamPath') { Write-Host 'register_driver has Get-SteamPath' } else { throw 'Missing Get-SteamPath' } }"</automated>
  </verify>
  <done>Both PowerShell scripts detect Steam path from registry with fallback to current hardcoded defaults. No behavior change for standard Steam installs.</done>
</task>

<task type="auto">
  <name>Task 2: Add Steam path detection to Inno Setup installer</name>
  <files>installer/BeyondProximity.iss</files>
  <action>
Inno Setup supports registry reads natively via `{reg:...}` constants and Pascal Script
`RegQueryStringValue`. Use Pascal Script for flexibility.

**Changes to [Setup] section:**
- Line 29: Change `DefaultDirName` to use a scripted constant:
  ```
  DefaultDirName={code:GetDefaultDirName}
  ```

**Changes to [Run] section:**
- Lines 60-64: Replace the hardcoded vrpathreg path with a scripted constant:
  ```
  Filename: "{code:GetVrPathRegPath}"; \
    Parameters: "adddriver ""{app}\bin\BeyondProximity"""; \
    Flags: runhidden waituntilterminated; \
    StatusMsg: "Registering driver with SteamVR..."; \
    Check: VrPathRegExists
  ```

**Changes to [Code] section -- add these functions BEFORE existing functions:**

```pascal
// ---------------------------------------------------------------------------
// GetSteamInstallPath - Detect Steam install location from Windows Registry.
// Checks HKLM (64-bit view) first, then HKCU. Returns empty string if not found.
// ---------------------------------------------------------------------------
function GetSteamInstallPath: String;
var
  SteamPath: String;
begin
  Result := '';
  // Try HKLM (most reliable -- set by Steam installer)
  if RegQueryStringValue(HKLM, 'SOFTWARE\WOW6432Node\Valve\Steam', 'InstallPath', SteamPath) then
  begin
    if SteamPath <> '' then
    begin
      Result := SteamPath;
      Log('Steam path from HKLM: ' + Result);
      Exit;
    end;
  end;
  // Try HKCU (user-level -- may use forward slashes)
  if RegQueryStringValue(HKCU, 'SOFTWARE\Valve\Steam', 'SteamPath', SteamPath) then
  begin
    if SteamPath <> '' then
    begin
      StringChangeEx(SteamPath, '/', '\', True);
      Result := SteamPath;
      Log('Steam path from HKCU: ' + Result);
      Exit;
    end;
  end;
  Log('Steam path not found in registry, using default');
end;

// ---------------------------------------------------------------------------
// GetDefaultDirName - Scripted constant for DefaultDirName.
// Uses detected Steam path or falls back to {autopf}\Steam.
// ---------------------------------------------------------------------------
function GetDefaultDirName(Param: String): String;
var
  SteamPath: String;
begin
  SteamPath := GetSteamInstallPath;
  if SteamPath <> '' then
    Result := SteamPath + '\steamapps\common\Bigscreen Beyond Driver'
  else
    Result := ExpandConstant('{autopf}') + '\Steam\steamapps\common\Bigscreen Beyond Driver';
end;

// ---------------------------------------------------------------------------
// GetVrPathRegPath - Returns full path to vrpathreg.exe using detected Steam path.
// ---------------------------------------------------------------------------
function GetVrPathRegPath(Param: String): String;
var
  SteamPath: String;
begin
  SteamPath := GetSteamInstallPath;
  if SteamPath <> '' then
    Result := SteamPath + '\steamapps\common\SteamVR\bin\win64\vrpathreg.exe'
  else
    Result := ExpandConstant('{autopf}') + '\Steam\steamapps\common\SteamVR\bin\win64\vrpathreg.exe';
end;

// ---------------------------------------------------------------------------
// VrPathRegExists - Check function for [Run] section: does vrpathreg.exe exist?
// ---------------------------------------------------------------------------
function VrPathRegExists: Boolean;
begin
  Result := FileExists(GetVrPathRegPath(''));
end;
```

Important: The `{code:FunctionName}` syntax calls Pascal Script functions at runtime. The
`Param: String` parameter is required by Inno Setup convention even if unused.
  </action>
  <verify>
    <automated>powershell -NoProfile -Command "$c = Get-Content installer/BeyondProximity.iss -Raw; $checks = @('GetSteamInstallPath', 'GetDefaultDirName', 'GetVrPathRegPath', 'VrPathRegExists', 'WOW6432Node', '{code:GetDefaultDirName}'); $missing = $checks | Where-Object { $c -notmatch [regex]::Escape($_) }; if ($missing) { throw ('Missing: ' + ($missing -join ', ')) } else { Write-Host 'All Inno Setup functions present' }"</automated>
  </verify>
  <done>Inno Setup installer detects Steam path from registry. DefaultDirName resolves to actual Steam location. vrpathreg.exe path resolves dynamically. Falls back to Program Files default if registry keys absent.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Registry -> scripts | Reading registry values controlled by Steam installer |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-quick-01 | Tampering | Registry keys | accept | Registry values are written by Steam installer; a user who tampers with their own registry is not a threat model we defend against. Path validation (Test-Path / FileExists) catches bad paths. |
| T-quick-02 | Information Disclosure | Steam install path | accept | Steam install path is not sensitive information. Logged for debugging. |
</threat_model>

<verification>
1. On a machine with standard Steam install: all three files should resolve to `C:\Program Files (x86)\Steam\...` paths (same as before)
2. On a machine with non-standard Steam install: paths should resolve to the actual Steam location
3. If Steam registry keys are missing entirely: falls back to hardcoded defaults
</verification>

<success_criteria>
- All three files read Steam path from `HKLM:\SOFTWARE\WOW6432Node\Valve\Steam\InstallPath` with HKCU fallback
- All three files fall back to current hardcoded paths when registry keys are absent
- No hardcoded `C:\Program Files (x86)\Steam` remains as a primary path (only as fallback)
- Installer, deploy, and register scripts all function identically on standard Steam installs
</success_criteria>

<output>
After completion, create `.planning/quick/260409-roi-update-installer-for-non-standard-steam-/260409-roi-SUMMARY.md`
</output>
