22295ed0创建于 2024年12月11日历史提交


File: Installer.ps1

## Fun fact, most of this code is generated entirely using GPT-3.5  ## CHATGPT PROMPT 1 ## Explain to me how to parse a conf file in PowerShell.  ## AI EXPLAINS HOW... this is important as it invokes reflective thinking. ## Having AI explain things to us first before asking  ## your question, significantly improves the quality of the response.   ### PROMPT 2 ### Okay, using this conf, can you write a powershell script that saves a new value to the global_prep_cmd?  ### AI Generates valid code for saving to conf file  ## Prompt 3 ### I think I have found a mistake, can you double check your work?  ## Again, this is important for reflective thinking, having the AI ## check its work is important, as it may improve quality.   ## Response: Did not find any errors.  ## Prompt 4: I tried this and unfortunately my config file requires admin to save.  ## AI Responses solutions  ## Like before, I already knew the solution but having the AI ## respond with tips, greatly improves the quality of the next prompts  ## Prompt 5 (Final with GPT3.5): Can you make this script self elevate itself. ## Repeat the same prompt principles, and basically 70% of this script is entirely written by Artificial Intelligence. Yay!  ## Refactor Prompt (GPT-4): Please refactor the following code, remove duplication and define better function names, once finished you will also add documentation and comments to each function. param($install) $filePath = $($MyInvocation.MyCommand.Path) $scriptRoot = Split-Path $filePath -Parent $scriptPath = "$scriptRoot\ResolutionMatcher.ps1"   # This script modifies the global_prep_cmd setting in the Sunshine configuration file # to add a command that runs ResolutionMatcher.ps1  # Check if the current user has administrator privileges $isAdmin = [bool]([System.Security.Principal.WindowsIdentity]::GetCurrent().groups -match 'S-1-5-32-544')  # If the current user is not an administrator, re-launch the script with elevated privileges if (-not $isAdmin) {     Start-Process powershell.exe  -Verb RunAs -ArgumentList "-ExecutionPolicy Bypass -NoExit -File `"$filePath`" $install"     exit }  function Test-AndRequest-SunshineConfig {     param(         [string]$InitialPath     )      # Check if the initial path exists     if (Test-Path $InitialPath) {         Write-Host "File found at: $InitialPath"         return $InitialPath     }     else {         # Show error message dialog         [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null         [System.Windows.Forms.MessageBox]::Show("Sunshine configuration could not be found. Please locate it.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)  | Out-Null          # Open file dialog         $fileDialog = New-Object System.Windows.Forms.OpenFileDialog         $fileDialog.Title = "Open sunshine.conf"         $fileDialog.Filter = "Configuration files (*.conf)|*.conf"         $fileDialog.InitialDirectory = [System.IO.Path]::GetDirectoryName($InitialPath)          if ($fileDialog.ShowDialog() -eq "OK") {             $selectedPath = $fileDialog.FileName             # Check if the selected path is valid             if (Test-Path $selectedPath) {                 Write-Host "File selected: $selectedPath"                 return $selectedPath             }             else {                 Write-Error "Invalid file path selected."             }          }         else {             Write-Error "Sunshine Configuiration file dialog was canceled or no valid file was selected."             exit 1         }     } }          # Define the path to the Sunshine configuration file $confPath = Test-AndRequest-SunshineConfig -InitialPath  "C:\Program Files\Sunshine\config\sunshine.conf" $scriptRoot = Split-Path $scriptPath -Parent    # Get the current value of global_prep_cmd from the configuration file function Get-GlobalPrepCommand {      # Read the contents of the configuration file into an array of strings     $config = Get-Content -Path $confPath      # Find the line that contains the global_prep_cmd setting     $globalPrepCmdLine = $config | Where-Object { $_ -match '^global_prep_cmd\s*=' }      # Extract the current value of global_prep_cmd     if ($globalPrepCmdLine -match '=\s*(.+)$') {         return $matches[1]     }     else {         Write-Information "Unable to extract current value of global_prep_cmd, this probably means user has not setup prep commands yet."         return [object[]]@()     } }  # Remove any existing commands that contain ResolutionMatcher from the global_prep_cmd value function Remove-ResolutionMatcherCommand {      # Get the current value of global_prep_cmd as a JSON string     $globalPrepCmdJson = Get-GlobalPrepCommand -ConfigPath $confPath      # Convert the JSON string to an array of objects     $globalPrepCmdArray = $globalPrepCmdJson | ConvertFrom-Json     $filteredCommands = @()      # Remove any ResolutionMatcher Commands     for ($i = 0; $i -lt $globalPrepCmdArray.Count; $i++) {         if (-not ($globalPrepCmdArray[$i].do -like "*ResolutionMatcher*")) {             $filteredCommands += $globalPrepCmdArray[$i]         }     }      return [object[]]$filteredCommands }  # Set a new value for global_prep_cmd in the configuration file function Set-GlobalPrepCommand {     param (          # The new value for global_prep_cmd as an array of objects         [object[]]$Value     )      if ($null -eq $Value) {         $Value = [object[]]@()     }       # Read the contents of the configuration file into an array of strings     $config = Get-Content -Path $confPath      # Get the current value of global_prep_cmd as a JSON string     $currentValueJson = Get-GlobalPrepCommand -ConfigPath $confPath      # Convert the new value to a JSON string     $newValueJson = ConvertTo-Json -InputObject $Value -Compress      # Replace the current value with the new value in the config array     try {         $config = $config -replace [regex]::Escape($currentValueJson), $newValueJson     }     catch {         # If it failed, it probably does not exist yet.         # In the event the config only has one line, we will cast this to an object array so it appends a new line automatically.          if ($Value.Length -eq 0) {             [object[]]$config += "global_prep_cmd = []"         }         else {             [object[]]$config += "global_prep_cmd = $($newValueJson)"         }     }        # Write the modified config array back to the file     $config | Set-Content -Path $confPath -Force }  # Add a new command to run ResolutionMatcher.ps1 to the global_prep_cmd value function Add-ResolutionMatcherCommand {      # Remove any existing commands that contain ResolutionMatcher from the global_prep_cmd value     $globalPrepCmdArray = Remove-ResolutionMatcherCommand -ConfigPath $confPath      # Create a new object with the command to run ResolutionMatcher.ps1     $ResolutionMatcherCommand = [PSCustomObject]@{         do       = "powershell.exe -executionpolicy bypass -WindowStyle Hidden -file `"$($scriptPath)`""         elevated = "false"         undo     = "powershell.exe -executionpolicy bypass -WindowStyle Hidden -file `"$($scriptRoot)\ResolutionMatcher-Functions.ps1`" $true"     }      # Add the new object to the global_prep_cmd array     [object[]]$globalPrepCmdArray += $ResolutionMatcherCommand      return [object[]]$globalPrepCmdArray } $commands = @() if ($install -eq "True") {     $commands = Add-ResolutionMatcherCommand } else {     $commands = Remove-ResolutionMatcherCommand  }   Set-GlobalPrepCommand $commands  $sunshineService = Get-Service -ErrorAction Ignore | Where-Object { $_.Name -eq 'sunshinesvc' -or $_.Name -eq 'SunshineService' } # In order for the commands to apply we have to restart the service $sunshineService | Restart-Service  -WarningAction SilentlyContinue Write-Host "If you didn't see any errors, that means the script installed without issues! You can close this window." 

File: ResolutionMatcher-Functions.ps1

param($terminate) Set-Location (Split-Path $MyInvocation.MyCommand.Path -Parent)  Add-Type -Path .\internals\DisplaySettings.cs  # If reverting the resolution fails, you can set a manual override here. $host_resolution_override = @{     Width   = 0     Height  = 0     Refresh = 0 }  ## Code and type generated with ChatGPT v4, 1st prompt worked flawlessly. Function Set-ScreenResolution($width, $height, $frequency) {      Write-Host "Setting screen resolution to $width x $height x $frequency"     $tolerance = 2 # Set the tolerance value for the frequency comparison     $devMode = New-Object DisplaySettings+DEVMODE     $devMode.dmSize = [System.Runtime.InteropServices.Marshal]::SizeOf($devMode)     $modeNum = 0      while ([DisplaySettings]::EnumDisplaySettings([NullString]::Value, $modeNum, [ref]$devMode)) {         $frequencyDiff = [Math]::Abs($devMode.dmDisplayFrequency - $frequency)         if ($devMode.dmPelsWidth -eq $width -and $devMode.dmPelsHeight -eq $height -and $frequencyDiff -le $tolerance) {             $result = [DisplaySettings]::ChangeDisplaySettings([ref]$devMode, 0)             if ($result -eq 0) {                 Write-Host "Resolution changed successfully."             }             else {                 Write-Host "Failed to change resolution. Error code: $result"             }             break         }         $modeNum++     } }  function Get-HostResolution {     $devMode = New-Object DisplaySettings+DEVMODE     $devMode.dmSize = [System.Runtime.InteropServices.Marshal]::SizeOf($devMode)     $modeNum = -1      while ([DisplaySettings]::EnumDisplaySettings([NullString]::Value, $modeNum, [ref]$devMode)) {         return @{             Width   = $devMode.dmPelsWidth             Height  = $devMode.dmPelsHeight             Refresh = $devMode.dmDisplayFrequency         }     } } function Assert-ResolutionChange($width, $height, $refresh) {     # Attempt to set the resolution up to 6 times, in event of failures     for ($i = 0; $i -lt 12; $i++) {         $hostResolution = Get-HostResolution         $refreshDiff = [Math]::Abs($hostResolution.Refresh - $refresh)         if (($width -ne $hostResolution.Width) -or ($height -ne $hostResolution.Height) -or ($refreshDiff -ge 2)) {             # If the resolutions don't match, set the screen resolution to the current client's resolution             Write-Host "Current Resolution: $($hostResolution.Width) x $($hostResolution.Height) x $($hostResolution.Refresh)"             Write-Host "Expected Requested Resolution: $width x $height x $refresh"             Set-ScreenResolution $width $height $refresh         }         # Wait for a while before checking the resolution again         Start-Sleep -Milliseconds 500     } }   function Join-Overrides($width, $height, $refresh) {     Write-Host "Before Override: $width x $height x $refresh"     $overrides = Get-Content ".\overrides.txt" -ErrorAction SilentlyContinue     foreach ($line in $overrides) {         if ($null -ne $line -and "" -ne $line) {             $overrides = $line | Select-String "(?<width>\d{1,})x(?<height>\d*)x?(?<refresh>\d*)?" -AllMatches                  $heights = $overrides[0].Matches.Groups | Where-Object { $_.Name -eq 'height' }             $widths = $overrides[0].Matches.Groups | Where-Object { $_.Name -eq 'width' }             $refreshes = $overrides[0].Matches.Groups | Where-Object { $_.Name -eq 'refresh' }              if ($widths[0].Value -eq $width -and $heights[0].Value -eq $height -and $refreshes[0].Value -eq $refresh) {                 $width = $widths[1].Value                 $height = $heights[1].Value                 $refresh = $refreshes[1].Value                 break             }         }          }      Write-Host "After Override: $width x $height x $refresh"     return @{         height  = $height         width   = $width         refresh = $refresh     } }    function IsCurrentlyStreaming() {     return $null -ne (Get-NetUDPEndpoint -OwningProcess (Get-Process sunshine).Id -ErrorAction Ignore) }    function Stop-ResolutionMatcherScript() {      $pipeExists = Get-ChildItem -Path "\\.\pipe\" | Where-Object { $_.Name -eq "ResolutionMatcher" }      if ($pipeExists.Length -gt 0) {         $pipeName = "ResolutionMatcher"         $pipe = New-Object System.IO.Pipes.NamedPipeClientStream(".", $pipeName, [System.IO.Pipes.PipeDirection]::Out)         $pipe.Connect(5)         $streamWriter = New-Object System.IO.StreamWriter($pipe)         $streamWriter.WriteLine("Terminate")         try {             $streamWriter.Flush()             $streamWriter.Dispose()             $pipe.Dispose()         }         catch {             # We don't care if the disposal fails, this is common with async pipes.             # Also, this powershell script will terminate anyway.         }     } }  function OnStreamStart($width, $height, $refresh) {     $expectedRes = Join-Overrides -width $width -height $height -refresh $refresh     Set-ScreenResolution -Width $expectedRes.Width -Height $expectedRes.Height -Freq $expectedRes.Refresh     Assert-ResolutionChange -width $expectedRes.Width -height $expectedRes.Height -refresh $expectedRes.Refresh }  function OnStreamEnd($hostResolution) {      if (($host_resolution_override.Values | Measure-Object -Sum).Sum -gt 1000) {         $hostResolution = @{             Width   = $host_resolution_override['Width']             Height  = $host_resolution_override['Height']             Refresh = $host_resolution_override['Refresh']         }     }     Set-ScreenResolution -Width $hostResolution.Width -Height $hostResolution.Height -Freq $hostResolution.Refresh    }        if ($terminate) {     Stop-ResolutionMatcherScript | Out-Null }

File: ResolutionMatcher.ps1

param($async) $path = (Split-Path $MyInvocation.MyCommand.Path -Parent) Set-Location $path $settings = Get-Content -Path .\settings.json | ConvertFrom-Json  # Since pre-commands in sunshine are synchronous, we'll launch this script again in another powershell process if ($null -eq $async) {     Start-Process powershell.exe  -ArgumentList "-ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`" $($MyInvocation.MyCommand.UnboundArguments) -async $true" -WindowStyle Hidden     exit }  Start-Transcript -Path .\log.txt . .\ResolutionMatcher-Functions.ps1 $hostResolutions = Get-HostResolution $lock = $false    $mutexName = "ResolutionMatcher" $resolutionMutex = New-Object System.Threading.Mutex($false, $mutexName, [ref]$lock)  # There is no need to have more than one of these scripts running. if (-not $resolutionMutex.WaitOne(0)) {     Write-Host "Another instance of the script is already running. Exiting..."     exit }  try {          # Asynchronously start the ResolutionMatcher, so we can use a named pipe to terminate it.     Start-Job -Name ResolutionMatcherJob -ScriptBlock {         param($path, $revertDelay)         . $path\ResolutionMatcher-Functions.ps1         $lastStreamed = Get-Date           Register-EngineEvent -SourceIdentifier ResolutionMatcher -Forward         New-Event -SourceIdentifier ResolutionMatcher -MessageData "Start"         while ($true) {             try {                 if ((IsCurrentlyStreaming)) {                     $lastStreamed = Get-Date                 }                 else {                     if (((Get-Date) - $lastStreamed).TotalSeconds -gt $revertDelay) {                         New-Event -SourceIdentifier ResolutionMatcher -MessageData "End"                         break;                     }                          }             }             finally {                 Start-Sleep -Seconds 1             }         }          } -ArgumentList $path, $settings.revertDelay       # To allow other powershell scripts to communicate to this one.     Start-Job -Name "ResolutionMatcher-Pipe" -ScriptBlock {         $pipeName = "ResolutionMatcher"         Remove-Item "\\.\pipe\$pipeName" -ErrorAction Ignore         $pipe = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName, [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Byte, [System.IO.Pipes.PipeOptions]::Asynchronous)          $streamReader = New-Object System.IO.StreamReader($pipe)         Write-Output "Waiting for named pipe to recieve kill command"         $pipe.WaitForConnection()          $message = $streamReader.ReadLine()         if ($message -eq "Terminate") {             Write-Output "Terminating pipe..."             $pipe.Dispose()             $streamReader.Dispose()         }     }        $eventMessageCount = 0     Write-Host "Waiting for the next event to be called... (for starting/ending stream)"     while ($true) {         $eventMessageCount += 1         Start-Sleep -Seconds 1         $eventFired = Get-Event -SourceIdentifier ResolutionMatcher -ErrorAction SilentlyContinue         $pipeJob = Get-Job -Name "ResolutionMatcher-Pipe"         if ($null -ne $eventFired) {             $eventName = $eventFired.MessageData             Write-Host "Processing event: $eventName"             if ($eventName -eq "Start") {                 OnStreamStart -width $env:SUNSHINE_CLIENT_WIDTH -height $env:SUNSHINE_CLIENT_HEIGHT -refresh $env:SUNSHINE_CLIENT_FPS              }             elseif ($eventName -eq "End") {                 OnStreamEnd $hostResolutions                 break;             }             Remove-Event -EventIdentifier $eventFired.EventIdentifier         }         elseif ($pipeJob.State -eq "Completed") {             Write-Host "Request to terminate has been processed, script will now revert resolution."             OnStreamEnd $hostResolutions             break;         }         elseif($eventMessageCount -gt 59) {             Write-Host "Still waiting for the next event to fire..."             $eventMessageCount = 0         }           } } finally {     Remove-Item "\\.\pipe\ResolutionMatcher" -ErrorAction Ignore     $resolutionMutex.ReleaseMutex()     Remove-Event -SourceIdentifier ResolutionMatcher -ErrorAction Ignore     Stop-Transcript }