Advertisement
aveyo

CS2_launcher

Feb 19th, 2024 (edited)
3,565
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Batch 48.00 KB | Gaming | 0 0
  1. @(echo off% <#%) & goto :start Counter-Strike 2
  2.  
  3.   sets screen resolution before launching the game, to alleviate input lag, alt-tab & windows on secondary screens issues
  4.   when res matches, both Desktop-friendly and Exclusive Fullscreen have low input lag and fast alt-tab (win-tab is better)
  5.   once the game is closed, restores the resolution to previous, or max if using option further below
  6.   + clears steam verify game integrity after a crash flags to relaunch quicker, hopefully preventing a timeout
  7.   + alleviates missing settings under connection problems / cloud conflicts / roaming profiles
  8.   + overrides video settings not present in ui to set lower than low quality with competitive shadows still visible
  9.   + proper handling of multiple screens, game starts on $force_screen or current screen even if not set as primary
  10.   + option to not force settings and just deal with matching the res; generate cfg if missing; improved shadow preset
  11.  
  12. #>)["AveYo, 2024.03.27"]
  13.  
  14. #:: gather freshest keys / user / machine .vcfg into cloud.cfg for applying settings when offline: 1  no: 0
  15. $cloud_cfg       =  1
  16.  
  17. #:: override fullscreen optimizations (FSO), enable: 1 disable: 0
  18. $enable_fso      =  0
  19.  
  20. #:: override screen or use current -1 | this is 1st number in the screen list; second number is for -sdl_displayindex
  21. $force_screen    = -1
  22.  
  23. #:: override fullscreen mode, exclusive: 1 desktop-friendly: 0
  24. $force_exclusive =  0
  25.  
  26. #:: override resolution, no: -1 max: 0 | if not appearing in res list, you must create the custom res in gpu driver settings
  27. #:: fastest res with ok scaling: 1024x768 4:3 or custom: 960x768 5:4, 1280x800 16:10, 1200x960 5:4, 1280x960 4:3, 1536x864 16:9
  28. $force_width     = -1
  29. $force_height    = -1
  30. $force_refresh   = -1
  31.  
  32. #:: override video settings with the preset below: yes 1; no -1 or 0
  33. $force_settings  =  1
  34.  
  35. #:: override specific video settings - prefix with # lines to remain unchanged
  36. $video = @{                                                           #  Shadow of a Potato preset
  37. # "setting.mat_vsync"                                = "0"
  38.   "setting.shaderquality"                            = "0"            #  0      more jpeg: 0     i : fps: 0 | smooth shadows: 1
  39. # "setting.r_aoproxy_enable"                         = "1"
  40. # "setting.r_aoproxy_min_dist"                       = "3"
  41.   "setting.r_ssao"                                   = "0"            #  0
  42.   "setting.r_csgo_lowend_objects"                    = "1"            #  1
  43.   "setting.r_texturefilteringquality"                = "3"            #  3      more jpeg: 0     i : trilinear: 1 | anyso16x: 5
  44.   "setting.r_character_decal_resolution"             = "256"          #  256    more jpeg: 128   i : medium model: 512
  45.   "setting.r_texture_stream_max_resolution"          = "1024"         #  1024   more jpeg: 512   i : medium texture: 2048
  46. # "setting.msaa_samples"                             = "2"
  47.   "setting.r_csgo_cmaa_enable"                       = "0"            #  0      more jpeg: 1     i : enable msaa 2 instead
  48.   "setting.csm_max_num_cascades_override"            = "2"            #  2
  49.   "setting.csm_viewmodel_shadows"                    = "0"            #  0
  50.   "setting.csm_max_shadow_dist_override"             = "480"          #  480    more jpeg: 240   i : player shadow from distance
  51.   "setting.lb_csm_override_staticgeo_cascades"       = "0"            #  0
  52.   "setting.lb_csm_override_staticgeo_cascades_value" = "0"            #  0
  53.   "setting.lb_sun_csm_size_cull_threshold_texels"    = "30.000000"    #  30
  54.   "setting.lb_shadow_texture_width_override"         = "0"            #  0
  55.   "setting.lb_shadow_texture_height_override"        = "0"            #  0
  56.   "setting.lb_csm_cascade_size_override"             = "640"          #  640    more jpeg: 320   i : fps: 640 | quality: 1280
  57.   "setting.lb_barnlight_shadowmap_scale"             = "1.000000"     #  1.0
  58.   "setting.lb_csm_draw_alpha_tested"                 = "1"            #  1
  59.   "setting.lb_csm_draw_translucent"                  = "0"            #  0
  60.   "setting.r_particle_cables_cast_shadows"           = "0"            #  0
  61.   "setting.lb_enable_shadow_casting"                 = "1"            #  1
  62.   "setting.lb_csm_cross_fade_override"               = "0.100000"     #  0.1
  63.   "setting.lb_csm_distance_fade_override"            = "0.050000"     #  0.05
  64.   "setting.r_particle_shadows"                       = "0"            #  0
  65.   "setting.cl_particle_fallback_base"                = "5"            #  5
  66.   "setting.cl_particle_fallback_multiplier"          = "5.000000"     #  5
  67.   "setting.r_particle_max_detail_level"              = "0"            #  0
  68.   "setting.r_csgo_mboit_force_mixed_resolution"      = "0"            #  0      more jpeg: 1     i : tracers lq: 1 | auto: 0
  69.   "setting.r_csgo_fsr_upsample"                      = "0"            #  0
  70.   "setting.mat_viewportscale"                        = "1.000000"     #  1.00   more jpeg: 0.71  i : fps+ quality--
  71.   "setting.sc_hdr_enabled_override"                  = "-1"           #  -1     more jpeg: 3     i : 8bit: 3 | auto: -1
  72.   "setting.r_low_latency"                            = "1"            #  1
  73. }
  74. $machine = @{
  75.   "r_player_visibility_mode"                         = "0"            #  0                       i : not in cs2_video.txt
  76. }
  77.  
  78. #:: override script handling or use default 0
  79. $do_not_set_desktop_res_to_match_game = 0
  80. $do_not_restore_res_use_max_available = 0
  81. $do_not_hide_script_window_on_waiting = 0
  82.  
  83. <# -------------------------------------------------------------------------------------------------------------------------------
  84. :start batch script section
  85.  
  86. set "APPID=730"
  87. set "APPNAME=cs2"
  88. set "GAMENAME=Counter-Strike Global Offensive"
  89. set "GAMEROOT=game\csgo"
  90. set "LAUNCHER=game\bin\win64\%APPNAME%.exe"
  91. set "M_CONFIG=%APPNAME%_machine_convars.vcfg"
  92. set "U_CONFIG=%APPNAME%_user_convars_0_slot0.vcfg"
  93. set "K_CONFIG=%APPNAME%_user_keys_0_slot0.vcfg"
  94. set "V_CONFIG=%APPNAME%_video.txt"
  95.  
  96. color 0b & title %APPNAME% launcher...
  97. tasklist /FI "imagename eq %APPNAME%.exe" | findstr "%APPNAME%.exe" >nul && (echo; %APPNAME% is running & timeout /t 3 & exit /b)
  98. taskkill /FI "windowtitle eq %APPNAME% launcher"
  99.  
  100. :: detect STEAM path
  101. for /f "tokens=2*" %%R in ('reg query HKCU\SOFTWARE\Valve\Steam /v SteamPath 2^>nul') do set "steam_reg=%%S"
  102. for %%S in ("%steam_reg%") do set "STEAM=%%~fS"
  103. :: detect GAMEROOT path
  104. set lib=& for /f usebackq^ delims^=^"^ tokens^=4 %%s in (`findstr /c:":\\" "%STEAM%\steamapps\libraryfolders.vdf"`) do (
  105.   if exist "%%s\steamapps\common\%GAMENAME%\%LAUNCHER%" set "lib=%%s")
  106. if defined lib (set "STEAMAPPS=%lib:\\=\%\steamapps") else echo; ERROR! %GAMENAME%\%LAUNCHER% not found & pause & exit /b
  107. set "GAMEROOT=%STEAMAPPS%\common\%GAMENAME%\%GAMEROOT%" & set "LAUNCHER=%STEAMAPPS%\common\%GAMENAME%\%LAUNCHER%"
  108. set "USRLOCAL=%GAMEROOT%"
  109. :: detect USRLOCALCSGO roaming profile path
  110. if defined USRLOCALCSGO if exist "%USRLOCALCSGO%\cfg\%M_CONFIG%" set "USRLOCAL=%USRLOCALCSGO%"
  111.  
  112. reg add "HKCU\Console\%~n0" /f /v ScreenColors /d 0xb /t reg_dword >nul 2>nul
  113. reg add "HKCU\Console\%~n0" /f /v QuickEdit /d 0 /t reg_dword >nul 2>nul
  114. set 1=%*& set "0=%~f0" & start "%APPNAME% launcher" conhost powershell -nop -noe -c iex ([io.file]::ReadAllText($env:0))
  115. goto :EOF
  116. :end batch script section
  117. ------------------------------------------------------------------------------------------------------------------------------- #>
  118. $env:DIR = split-path $env:0 # powershell code section
  119.  
  120. #:: detect CLOUD path - more reliable in ps than in cmd
  121. pushd "$env:STEAM\userdata"
  122. $env:CLOUD = split-path (dir "localconfig.vdf" -File -Recurse | sort LastWriteTime -Descending | Select -First 1).DirectoryName
  123. if ($env:USRLOCAL -ne $env:USRLOCALCSGO) { $env:USRLOCAL = "$env:CLOUD\$env:APPID\local" }
  124. popd
  125.  
  126. #:: use freshest config across supported locations
  127. robocopy "$env:GAMEROOT\cfg/" "$env:USRLOCAL\cfg/" $env:M_CONFIG $env:U_CONFIG $env:K_CONFIG $env:V_CONFIG /XO >''
  128. robocopy "$env:CLOUD\$env:APPID\local\cfg/" "$env:USRLOCAL\cfg/" $env:M_CONFIG $env:U_CONFIG $env:K_CONFIG $env:V_CONFIG /XO >''
  129. robocopy "$env:USRLOCALCSGO\cfg/" "$env:USRLOCAL\cfg/" $env:M_CONFIG $env:U_CONFIG $env:K_CONFIG $env:V_CONFIG /XO >''
  130.  
  131. #:: export steam userdata configs to %gameroot%\cfg\cloud.cfg - then can exec cloud to restore missing settings
  132. if ($cloud_cfg -gt 0) {
  133.   sc "$env:GAMEROOT\cfg\cloud.cfg" "// steam cloud settings from $env:K_CONFIG & $env:U_CONFIG & $env:M_CONFIG`r`n" -force -ea 0
  134.   $cloud_cfg = $false
  135.   $keys_vcfg = "$env:USRLOCAL\cfg\$env:K_CONFIG"; $cfg = new-object System.Text.StringBuilder
  136.   if (test-path $keys_vcfg) {
  137.     gc $keys_vcfg |foreach { $l = $_ -split '"'; if ($l.count -eq 5) { # -and $l[3] -ne '<unbound>'
  138.       $cfg.Append('bind "')>''; $cfg.Append($l[1])>''; $cfg.Append('" "')>''; $cfg.Append($l[3])>''
  139.     $cfg.AppendLine('" | grep %%')>'' } }
  140.     if ($cfg.length -gt 0) { ac -Literal "$env:GAMEROOT\cfg\cloud.cfg" -Value $cfg.ToString() }
  141.   }
  142.   $user_vcfg = "$env:USRLOCAL\cfg\$env:U_CONFIG"; $cfg = new-object System.Text.StringBuilder
  143.   if (test-path $user_vcfg) {
  144.     gc $user_vcfg |foreach { $l = $_ -split '"'; if ($l.count -eq 5) {
  145.       $cfg.Append($l[1])>''; $cfg.Append(' "')>''; $cfg.Append($l[3])>''; $cfg.AppendLine('"')>'' } }
  146.     if ($cfg.length -gt 0) { ac -Literal "$env:GAMEROOT\cfg\cloud.cfg" -Value $cfg.ToString() }
  147.   }
  148.   $machine_vcfg = "$env:USRLOCAL\cfg\$env:M_CONFIG"; $cfg = new-object System.Text.StringBuilder
  149.   if (test-path $machine_vcfg) {
  150.     gc $machine_vcfg |foreach { $l = $_ -split '"'; if ($l.count -eq 5) {
  151.       $cfg.Append($l[1].Split('$')[0])>''; $cfg.Append(' "')>''; $cfg.Append($l[3])>''; $cfg.AppendLine('"')>'' } }
  152.     if ($cfg.length -gt 0) { ac -Literal "$env:GAMEROOT\cfg\cloud.cfg" -Value $cfg.ToString() }
  153.   }
  154.   ac -Literal "$env:GAMEROOT\cfg\cloud.cfg" -Value "execifexists autoexec.cfg";
  155. }
  156.  
  157. #:: clear verify integrity flags after crash for quicker relaunch
  158. $appmanifest="$env:STEAMAPPS\appmanifest_${env:APPID}.acf"
  159. if (test-path $appmanifest) {
  160.   $ACF = [io.file]::ReadAllText($appmanifest)
  161.   if ($ACF -match '"FullValidateAfterNextUpdate"\s+"1"' -or $ACF -notmatch '"StateFlags"\s+"4"') {
  162.     " update or verify integrity flags detected, will clear them and restart Steam...`n"
  163.     'dota2','cs2','steamwebhelper','steam' |% {kill -name $_ -force -ea 0} ; sleep 3; del "$env:STEAM\.crash" -force -ea 0
  164.     $ACF = $ACF -replace '("FullValidateAfterNextUpdate"\s+)("\d+")',"`$1`"0`"" -replace '("StateFlags"\s+)("\d+")',"`$1`"4`""
  165.     [io.file]::WriteAllText($appmanifest, $ACF)
  166.   }
  167. } else {
  168.   sc $appmanifest @"
  169. "AppState"
  170. {
  171.   "AppID"  "$env:APPID"
  172.   "Universe" "1"
  173.   "installdir" "$env:GAMENAME"
  174.   "StateFlags" "4"
  175. }
  176. "@ -force; " $appmanifest missing or wrong lib path detected! continuing with a default manifest...`n";
  177. }
  178.  
  179. #:: toggle fullscreen optimizations for game launcher - FSO as a concept is an abomination - ofc it causes input lag
  180. $flags = 'HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers'
  181. $found = (gi $flags -ea Ignore).Property -contains $env:LAUNCHER
  182. $valid = $found -and (gpv $flags $env:LAUNCHER) -like '*DISABLEDXMAXIMIZEDWINDOWEDMODE*'
  183. if ($enable_fso -eq 0 -and (!$found -or !$valid)) {
  184.   " disabling per app os fullscreen (un)optimizations"
  185.   ni $flags -ea 0; sp $flags $env:LAUNCHER '~ DISABLEDXMAXIMIZEDWINDOWEDMODE HIGHDPIAWARE' -force -ea 0
  186. }
  187. if ($enable_fso -eq 1 -and $valid) {rp $flags $env:LAUNCHER -force -ea 0}
  188.  
  189. #:: warn if steam not already running
  190. if ($null -eq (get-process 'steam' -ea 0)) { " steam not running, will take a bit longer...`n" }
  191.  
  192. #:: parse game launch options
  193. $lo = (gc "$env:CLOUD\config\localconfig.vdf") -join "`n"
  194. $lo = (($lo -split '\n\s{5}"' + $env:APPID + '"\n\s{5}{\n')[1] -split '\n\s{5}}\n')[0]
  195. $lo = (($lo -split '\n\s{6}"LaunchOptions"\s+"')[1] -split '"\n')[0]
  196. $launcher_width = -1; $launcher_height = -1; $launcher_refresh = -1; $launcher_exclusive = -1; $launcher_screen = -1
  197. if ($lo -match '-w(idth)?\s+(\d+)')         { $launcher_width     = [int]$matches[2] }
  198. if ($lo -match '-h(eight)?\s+(\d+)')        { $launcher_height    = [int]$matches[2] }
  199. if ($lo -match '-r(efresh)?\s+([\d.]+)')    { $launcher_refresh   = [decimal]$matches[2] }
  200. if ($lo -match '-fullscreen\s+')            { $launcher_exclusive = 1 }
  201. if ($lo -match '-sdl_displayindex\s+(\d+)') { $launcher_screen    = [int]$matches[1] }
  202.  
  203. #:: parse video txt file
  204. $video_config = "$env:USRLOCAL\cfg\$env:V_CONFIG"
  205. $cfg_width = -1; $cfg_height = -1; $cfg_refresh = -1; $cfg_numer = -1; $cfg_denom = -1; $cfg_exclusive = -1
  206. if (test-path $video_config) {
  207.   $lines = (gc $video_config); $txt = $lines -join "`n"
  208.   if ($txt -match '"setting.defaultres"\s+"([^"]*)"')              { $cfg_width     = [int]$matches[1] }
  209.   if ($txt -match '"setting.defaultresheight"\s+"([^"]*)"')        { $cfg_height    = [int]$matches[1] }
  210.   if ($txt -match '"setting.refreshrate_numerator"\s+"([^"]*)"')   { $cfg_numer     = [int]$matches[1] }
  211.   if ($txt -match '"setting.refreshrate_denominator"\s+"([^"]*)"') { $cfg_denom     = [int]$matches[1] }
  212.   if ($txt -match '"setting.fullscreen"\s+"([^"]*)"')              { $cfg_exclusive = [int]$matches[1] }
  213.   #:: compute numerator / denominator = refresh for video txt file
  214.   if ($cfg_numer -gt 0 -and $cfg_denom -gt 0) { $cfg_refresh = [decimal]$cfg_numer / $cfg_denom } else { $cfg_refresh = 0 }
  215. }
  216.  
  217. #:: decide which sets of video options overrides to use: script has priority, then launch options, then cfg
  218. $width   = (0,$cfg_width)[$cfg_width -gt 0];   $height = (0,$cfg_height)[$cfg_height -gt 0]
  219. $refresh = 0;  $exclusive = $cfg_exclusive
  220. if ($launcher_width -ge 0)     {$width = $launcher_width}         ; if ($force_width -ge 0)     {$width = $force_width}
  221. if ($launcher_height -ge 0)    {$height = $launcher_height}       ; if ($force_height -ge 0)    {$height = $force_height}
  222. if ($launcher_refresh -ge 0)   {$refresh = $launcher_refresh}     ; if ($force_refresh -ge 0)   {$refresh = $force_refresh}
  223. if ($launcher_exclusive -ge 0) {$exclusive = $launcher_exclusive} ; if ($force_exclusive -ge 0) {$exclusive = $force_exclusive}
  224. if ($launcher_screen -ge 0)    {$screen_sdr = $launcher_screen}   ; if ($force_screen -ge 0)    {$screen = $force_screen}
  225. if ($refresh -gt 0) {
  226.   $hz = ([string]$refresh).Split('.'); $denom = 1000
  227.   if ($hz.length -eq 2) { $numer = [int]($hz[0] + $hz[1].PadRight(3,'0')) } else { $numer = [int]($hz[0] + "000") }
  228. }
  229.  
  230. #::  many thanks to /u/wazernet for testing and suggestions
  231. $mode = "$width x $height ${refresh}Hz " + ('Desktop-friendly','Exclusive')[$exclusive -gt 0] + (' + FSO','')[$enable_fso -eq 0]
  232. " launcher = $env:LAUNCHER"
  233. " gameroot = $env:GAMEROOT"
  234. " mode     = $mode `n"
  235.  
  236. #:: set screen resolution via SetRes before launching the game, to alleviate input lag, alt-tab and secondary screens issues
  237. $library1 = "SetRes"; $descripion1 = "set screen resolution"; $version1 = "2024.3.10.0"
  238. #:: [SetRes.Displays]::Change(output[0:none 1:def], screen, width, height, refresh[0:def], test[0:change 1:test])
  239. #:: [SetRes.Displays]::List(output[0:none 1:filter 2:all], screen, minw[1024], maxw[16384], maxh[16384])
  240. #:: returns array of sdl_idx, screen, current_width, current_height, current_refresh, max_width, max_height, max_refresh
  241. #:: will pre-compile the c# typefinition at the end of the script rather than powershell do it slowly every launch
  242. if ((gi "$env:APPDATA\AveYo\$library1.dll" -force -ea 0).VersionInfo.FileVersion -ne $version1) {
  243.   del "$env:APPDATA\AveYo\$library1.dll" -force -ea 0; del "$env:DIR\$library1.dll" -force -ea 0
  244. }
  245. if (-not (test-path "$env:APPDATA\AveYo\$library1.dll")) {
  246.   mkdir "$env:APPDATA\AveYo" -ea 0 >'' 2>''; pushd $env:APPDATA\AveYo; " one-time initialization of $library1 library..."
  247.   [io.file]::WriteAllText("$env:APPDATA\AveYo\$library1.cs", ([io.file]::ReadAllText($env:0) -split '<#[:]LIBRARY1[:].*')[1])
  248.   $csc = join-path $([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()) 'csc.exe'
  249.   start $csc -args "/out:$library1.dll /target:library /platform:anycpu /optimize /nologo $library1.cs" -nonew -wait; popd
  250. }
  251. Import-Module "$env:APPDATA\AveYo\$library1.dll"
  252. $display = [SetRes.Displays]::Init($screen)
  253. $sdl_idx = $display[0];  $screen = $display[1];  $primary = $display[2] -gt 0;  $multimon = $display[3] -gt 1
  254.  
  255. #:: restore previous resolution if game was not gracefully closed last time
  256. if ($do_not_set_desktop_res_to_match_game -le 0 -and (test-path "$env:GAMEROOT\cfg\SetRes.cfg")) {
  257.   $restore = (gc "$env:GAMEROOT\cfg\SetRes.cfg") -split ','
  258.   if ($null -eq (get-process $env:APPNAME -ea 0)) {
  259.     $c = [SetRes.Displays]::Change(0, $restore[1], $restore[2], $restore[3], $restore[4])
  260.   }
  261. }
  262.  
  263. #:: SetRes automatically picks a usable mode if the change is invalid so result might differ from the request
  264. $oldres  = [SetRes.Displays]::List(1, $screen)
  265. if ($width   -le 0) { $width  = $oldres[2] }
  266. if ($height  -le 0) { $height = $oldres[3] }
  267. if ($refresh -le 0) { $max_refresh = [SetRes.Displays]::List(0, $screen, $width, $width, $height); $refresh = $max_refresh[7] }
  268. $newres  = [SetRes.Displays]::Change(1, $screen, $width, $height, $refresh, 1)
  269. $width   = $newres[5]; $restore_width   = $newres[2]
  270. $height  = $newres[6]; $restore_height  = $newres[3]
  271. $refresh = $newres[7]; $restore_refresh = $newres[4]
  272. function max {$r = [SetRes.Displays]::Change(1, $oldres[1], $oldres[5], $oldres[6], $oldres[7])} # console command to set max res
  273. function min {$r = [SetRes.Displays]::Change(1, $oldres[1], 1024,       768,        $oldres[7])} # console command to set min res
  274. if ($do_not_restore_res_use_max_available -ge 1) {
  275.   $restore_width = $oldres[5]; $restore_height = $oldres[6]; $restore_refresh = $oldres[7]
  276. }
  277. $sameres = $width -eq $restore_width -and $height -eq $restore_height -and $refresh -eq $restore_refresh
  278. $ratio   = $width / $height
  279. if ($ratio -le 4/3) {$ar = 0} elseif ($ratio -le 16/10) {$ar = 2} elseif ($ratio -le 16/8.9) {$ar = 1} else {$ar = 3}
  280.  
  281. #:: update video overrides in case the initial mode was invalid and SetRes applied a fallback
  282. if ($force_settings -le 0) { $video = @{} }
  283. $video["setting.defaultres"]                   = $width
  284. $video["setting.defaultresheight"]             = $height
  285. $video["setting.refreshrate_numerator"]        = $refresh
  286. $video["setting.refreshrate_denominator"]      = 1
  287. $video["setting.fullscreen"]                   = (0,1)[$exclusive -eq 1]
  288. $video["setting.coop_fullscreen"]              = (0,1)[$exclusive -ne 1]
  289. $video["setting.nowindowborder"]               = 1
  290. $video["setting.fullscreen_min_on_focus_loss"] = 0
  291. $video["setting.aspectratiomode"]              = $ar
  292.  
  293. #:: update cfg files with the overrides
  294. $video_config = "$env:USRLOCAL\cfg\$env:V_CONFIG"
  295. if (-not (test-path $video_config)) {sc $video_config "`"video.cfg`"`n{`n`t`"Version`"`t`t`"12`"`n}`n" -force -ea 0 }
  296. if ((test-path $video_config) -and $force_settings -ge 1) {
  297.   $lines = (gc $video_config); $txt = $lines -join "`n"; $cfg = new-object System.Text.StringBuilder # dos line-endings
  298.   foreach ($k in $video.Keys) {
  299.     if ($k -like 'setting.*' -and $txt -notmatch "`"$k`"") { $cfg.Append("`r`n`t`"$k`"`t`t`"$($video.$k)`"")>'' }
  300.   }
  301.   if ($cfg.length -gt 0) { -1..-10 |foreach { if ($lines[$_] -match "^}$") { $lines[$_ - 1] += $cfg.ToString(); return } } }
  302.   if ($cfg.length -gt 0) {sc $video_config $lines -force -ea 0 }
  303.   (gc $video_config) |foreach {
  304.     foreach ($k in $video.Keys) { if ($_ -like "*$k`"*") {
  305.       $_ = $_ -replace "(`"$k`"\s+)(`"[^`"]*`")","`$1`"$($video.$k)`"" } }; $_ } | sc $video_config -force -ea 0
  306. }
  307. $machine_config = "$env:USRLOCAL\cfg\$env:M_CONFIG"
  308. if (-not (test-path $machine_config)) {sc $machine_config "`"config`"`n{`n`t`"convars`"`n`t{`n`t`t`n`t}`n}`n" -force -ea 0 }
  309. if ((test-path $machine_config) -and $force_settings -ge 1) {
  310.   $lines = (gc $machine_config); $txt = $lines -join "`n"; $cfg = new-object System.Text.StringBuilder # unix line-endings
  311.   foreach ($k in $machine.Keys) { if ($txt -notmatch "`"$k`"") { $cfg.Append("`n`t`t`"$k`"`t`t`"$($machine.$k)`"")>'' } }
  312.   if ($cfg.length -gt 0) { -1..-10 |foreach { if ($lines[$_] -match "^\s}$") { $lines[$_ - 1] += $cfg.ToString(); return } } }
  313.   if ($cfg.length -gt 0) { sc $machine_config (($lines -join "`n") + "`n") -noNewLine -force -ea 0 }
  314.   (gc $machine_config) |foreach {
  315.     foreach ($k in $machine.Keys) { if ($_ -like "*$k`"*") {
  316.       $_ = $_ -replace "(`"$k`"\s+)(`"[^`"]*`")","`$1`"$($machine.$k)`"" } }; $_ } | sc $machine_config -force -ea 0
  317. }
  318.  
  319. #:: prepare video launch options
  320. $window = @("-force_allow_coop_fullscreen -coop_fullscreen", "-force_allow_excl_fullscreen -fullscreen")[$exclusive -ge 1]
  321. $video_options = "$window -width $width -height $height -refresh $refresh -sdl_displayindex $sdl_idx"
  322. " options  = $video_options"
  323. #pause
  324.  
  325. #:: prepare steam quick options
  326. $quick = '-quicklogin -skipinitialbootstrap -skipstreamingdrivers -vrdisable -nofriendsui -oldtraymenu -cef-disable-gpu -silent'
  327. $steam_options = "$QUICK -applaunch $env:APPID $video_options "
  328. if ($cloud_cfg -ge 1) { $steam_options += "+exec_async cloud "}
  329.  
  330. #:: start game (and steam if not already running)
  331. powershell.exe -nop -c "Start-Process \`"$env:STEAM\steam.exe\`" \`"$steam_options\`""
  332.  
  333. #:: restore res after game closes if it was changed
  334. if ($do_not_set_desktop_res_to_match_game -le 0 -and -not $sameres) {
  335.   sc "$env:GAMEROOT\cfg\SetRes.cfg" "$sdl_idx,$screen,$restore_width,$restore_height,$restore_refresh,`r`n" -force -ea 0
  336.   "`n will restore res to $restore_width x $restore_height ${restore_refresh}Hz after $($env:APPNAME.ToUpper()) closes..."
  337.   while ($null -eq ($wait = get-process $env:APPNAME -ea 0)) { sleep -m 250 }
  338.   $change  = [SetRes.Displays]::Change(1, $screen, $width, $height, $refresh)
  339.   if ($do_not_hide_script_window_on_waiting -le 0) { sleep 5; powershell -win 1 -nop -c ';' }
  340.   while (-not $wait.HasExited) { sleep 5 }
  341.   $restore = [SetRes.Displays]::Change(1, $screen, $restore_width, $restore_height, $restore_refresh)
  342.   del "$env:GAMEROOT\cfg\SetRes.cfg" -force -ea 0
  343. } else {
  344.   #:: change even if res matches, to address a rare bug where game starts in a blank window and can only \ q-tab enter out of it
  345.   $change  = [SetRes.Displays]::Change(1, $screen, $restore_width, $restore_height, $restore_refresh)
  346. }
  347. " can enter: max for $($oldres[5])x$($oldres[6]) or: min for 1024x768 if needed"
  348.  
  349. #:: done, script closes
  350. if ($do_not_hide_script_window_on_waiting -ge 1) { return }
  351. [Environment]::Exit(0)
  352.  
  353. <#:LIBRARY1: start <# ------------------------------------------------------------------------------------------------------------
  354. /// SetRes - loosely based on code by Rick Strahl
  355. using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Linq; using System.Reflection;
  356. [assembly:AssemblyVersion("2024.3.10.0")] [assembly: AssemblyTitle("AveYo")]
  357. namespace SetRes
  358. {
  359.   public static class Displays
  360.   {
  361.     private const short CCDEVICENAME = 32,  CCFORMNAME  = 32;
  362.  
  363.     public const int SUCCESS       = 0,  ENUM_CURRENT  = -1,  MONITOR_DEFAULTTONEAREST = 0x00000002;
  364.     public const int DMDFO_DEFAULT = 0,  DMDFO_STRETCH =  1,  DMDFO_CENTER = 2;
  365.     public const int DMDO_DEFAULT  = 0,  DMDO_90       =  1,  DMDO_180     = 2,  DMDO_270 = 3;
  366.  
  367.     [Flags()]
  368.     private enum EdsFlags : int
  369.     {
  370.       EDS_ATTACHEDTODESKTOP = 0x00000001,  EDS_MULTIDRIVER   = 0x00000002,  EDS_PRIMARYDEVICE = 0x00000004,
  371.       EDS_MIRRORINGDRIVER   = 0x00000008,  EDS_VGACOMPATIBLE = 0x00000010,  EDS_REMOVABLE     = 0x00000020,
  372.       EDS_MODESPRUNED       = 0x08000000,  EDS_REMOTE        = 0x04000000,  EDS_DISCONNECT    = 0x02000000
  373.     }
  374.  
  375.     [Flags()]
  376.     private enum CdsFlags : uint
  377.     {
  378.       CDS_NONE            = 0x00000000,  CDS_UPDATEREGISTRY      = 0x00000001,  CDS_TEST                 = 0x00000002,
  379.       CDS_FULLSCREEN      = 0x00000004,  CDS_GLOBAL              = 0x00000008,  CDS_SET_PRIMARY          = 0x00000010,
  380.       CDS_VIDEOPARAMETERS = 0x00000020,  CDS_ENABLE_UNSAFE_MODES = 0x00000100,  CDS_DISABLE_UNSAFE_MODES = 0x00000200,
  381.       CDS_RESET           = 0x40000000,  CDS_RESET_EX            = 0x20000000,  CDS_NORESET             = 0x10000000
  382.     }
  383.  
  384.     [Flags()]
  385.     private enum DmFlags : int
  386.     {
  387.       DM_ORIENTATION   = 0x00000001,  DM_PAPERSIZE          = 0x00000002,  DM_PAPERLENGTH        = 0x00000004,
  388.       DM_PAPERWIDTH    = 0x00000008,  DM_SCALE              = 0x00000010,  DM_POSITION           = 0x00000020,
  389.       DM_NUP           = 0x00000040,  DM_DISPLAYORIENTATION = 0x00000080,  DM_COPIES             = 0x00000100,
  390.       DM_DEFAULTSOURCE = 0x00000200,  DM_PRINTQUALITY       = 0x00000400,  DM_COLOR              = 0x00000800,
  391.       DM_DUPLEX        = 0x00001000,  DM_YRESOLUTION        = 0x00002000,  DM_TTOPTION           = 0x00004000,
  392.       DM_COLLATE       = 0x00008000,  DM_FORMNAME           = 0x00010000,  DM_LOGPIXELS          = 0x00020000,
  393.       DM_BITSPERPEL    = 0x00040000,  DM_PELSWIDTH          = 0x00080000,  DM_PELSHEIGHT         = 0x00100000,
  394.       DM_DISPLAYFLAGS  = 0x00200000,  DM_DISPLAYFREQUENCY   = 0x00400000,  DM_ICMMETHOD          = 0x00800000,
  395.       DM_ICMINTENT     = 0x01000000,  DM_MEDIATYPE          = 0x02000000,  DM_DITHERTYPE         = 0x04000000,
  396.       DM_PANNINGWIDTH  = 0x08000000,  DM_PANNINGHEIGHT      = 0x10000000,  DM_DISPLAYFIXEDOUTPUT = 0x20000000
  397.     }
  398.  
  399.     [StructLayout(LayoutKind.Sequential)]
  400.     public struct POINTL { public int x; public int y; }
  401.  
  402.     [StructLayout(LayoutKind.Sequential)]
  403.     public struct RECT { public int left; public int top; public int right; public int bottom; }
  404.  
  405.     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
  406.     private struct DISPLAY_DEVICE
  407.     {
  408.       [MarshalAs(UnmanagedType.U4)]                       public int      cb;
  409.       [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]  public string   DeviceName;
  410.       [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)] public string   DeviceString;
  411.       [MarshalAs(UnmanagedType.U4)]                       public EdsFlags StateFlags;
  412.       [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)] public string   DeviceID;
  413.       [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)] public string   DeviceKey;
  414.       public void Initialize()
  415.       {
  416.         this.DeviceName   = new string(new char[32]);
  417.         this.DeviceString = new string(new char[128]);
  418.         this.DeviceID     = new string(new char[128]);
  419.         this.DeviceKey    = new string(new char[128]);
  420.         this.cb           = Marshal.SizeOf(this);
  421.       }
  422.     }
  423.  
  424.     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
  425.     private struct DEVMODE
  426.     {
  427.       [MarshalAs(UnmanagedType.ByValTStr, SizeConst=CCDEVICENAME)]
  428.                                     public string  dmDeviceName;
  429.       [MarshalAs(UnmanagedType.U2)] public ushort  dmSpecVersion;
  430.       [MarshalAs(UnmanagedType.U2)] public ushort  dmDriverVersion;
  431.       [MarshalAs(UnmanagedType.U2)] public ushort  dmSize;
  432.       [MarshalAs(UnmanagedType.U2)] public ushort  dmDriverExtra;
  433.       [MarshalAs(UnmanagedType.U4)] public DmFlags dmFields;
  434.                                     public POINTL  dmPosition;
  435.       [MarshalAs(UnmanagedType.U4)] public uint    dmDisplayOrientation;
  436.       [MarshalAs(UnmanagedType.U4)] public uint    dmDisplayFixedOutput;
  437.       [MarshalAs(UnmanagedType.I2)] public short   dmColor;
  438.       [MarshalAs(UnmanagedType.I2)] public short   dmDuplex;
  439.       [MarshalAs(UnmanagedType.I2)] public short   dmYResolution;
  440.       [MarshalAs(UnmanagedType.I2)] public short   dmTTOption;
  441.       [MarshalAs(UnmanagedType.I2)] public short   dmCollate;
  442.       [MarshalAs(UnmanagedType.ByValTStr, SizeConst=CCFORMNAME)]
  443.                                     public string  dmFormName;
  444.       [MarshalAs(UnmanagedType.U2)] public ushort  dmLogPixels;
  445.       [MarshalAs(UnmanagedType.U4)] public uint    dmBitsPerPel;
  446.       [MarshalAs(UnmanagedType.U4)] public uint    dmPelsWidth;
  447.       [MarshalAs(UnmanagedType.U4)] public uint    dmPelsHeight;
  448.       [MarshalAs(UnmanagedType.U4)] public uint    dmDisplayFlags;
  449.       [MarshalAs(UnmanagedType.U4)] public uint    dmDisplayFrequency;
  450.       [MarshalAs(UnmanagedType.U4)] public uint    dmICMMethod;
  451.       [MarshalAs(UnmanagedType.U4)] public uint    dmICMIntent;
  452.       [MarshalAs(UnmanagedType.U4)] public uint    dmMediaType;
  453.       [MarshalAs(UnmanagedType.U4)] public uint    dmDitherType;
  454.       [MarshalAs(UnmanagedType.U4)] public uint    dmReserved1;
  455.       [MarshalAs(UnmanagedType.U4)] public uint    dmReserved2;
  456.       [MarshalAs(UnmanagedType.U4)] public uint    dmPanningWidth;
  457.       [MarshalAs(UnmanagedType.U4)] public uint    dmPanningHeight;
  458.       public void Initialize()
  459.       {
  460.         this.dmDeviceName = new string(new char[CCDEVICENAME]);
  461.         this.dmFormName   = new string(new char[CCFORMNAME]);
  462.         this.dmSize       = (ushort)Marshal.SizeOf(this);
  463.       }
  464.     }
  465.  
  466.     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
  467.     private struct MONITORINFOEX
  468.     {
  469.       public uint cbSize;
  470.       public RECT rcMonitor;
  471.       public RECT rcWork;
  472.       public int dwFlags;
  473.       [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szDevice;
  474.       public void Initialize()
  475.       {
  476.         this.rcMonitor = new RECT();
  477.         this.rcWork    = new RECT();
  478.         this.szDevice  = new string(new char[32]);
  479.         this.cbSize    = (uint)Marshal.SizeOf(this);
  480.       }
  481.     }
  482.  
  483.     [DllImport("kernel32", ExactSpelling = true)] private static extern IntPtr
  484.     GetConsoleWindow();
  485.  
  486.     [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool
  487.     GetWindowRect(IntPtr hWnd, out RECT lpRect);
  488.  
  489.     [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool
  490.     MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
  491.  
  492.     [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool
  493.     GetCursorPos(out POINTL lpPoint);
  494.  
  495.     [DllImport("user32", SetLastError = true)] private static extern IntPtr
  496.     MonitorFromPoint(POINTL pt, int dwFlags);
  497.  
  498.     [DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)]
  499.     [return: MarshalAs(UnmanagedType.Bool)] private static extern bool
  500.     GetMonitorInfo(IntPtr hMonitor, [In, Out] ref MONITORINFOEX lpmi);
  501.  
  502.     [DllImport("user32", CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool
  503.     EnumDisplayMonitors(IntPtr hdc, IntPtr lpRect, EnumDisplayMonitorsDelegate lpfnEnum, IntPtr dwData);
  504.  
  505.     [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool
  506.     EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
  507.  
  508.     [DllImport("user32", SetLastError=true, BestFitMapping=false, ThrowOnUnmappableChar=true)]
  509.     [return: MarshalAs(UnmanagedType.Bool)] private static extern bool
  510.     EnumDisplaySettings(byte[] lpszDeviceName, [param: MarshalAs(UnmanagedType.U4)] int iModeNum, [In,Out] ref DEVMODE lpDevMode);
  511.  
  512.     [DllImport("user32")] private static extern int
  513.     ChangeDisplaySettingsEx(string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, CdsFlags dwflags, IntPtr lParam);
  514.  
  515.     //[DllImport("user32")] private static extern int
  516.     //ChangeDisplaySettingsEx(IntPtr lpszDeviceName, IntPtr lpDevMode, IntPtr hwnd, int dwflags, IntPtr lParam);
  517.  
  518.     [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool
  519.     SetProcessDPIAware();
  520.  
  521.     private delegate bool EnumDisplayMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);
  522.  
  523.     private static IntPtr consolehWnd = GetConsoleWindow();
  524.  
  525.     public static class StringExtensions
  526.     {
  527.       public static byte[] ToLPTStr(string str)
  528.       {
  529.         return (str == null) ? null : Array.ConvertAll((str + '\0').ToCharArray(), Convert.ToByte);
  530.       }
  531.     }
  532.  
  533.     public class DisplayInfo
  534.     {
  535.       public int    Index      { get; set; }
  536.       public int    SDLIndex   { get; set; }
  537.       public string DeviceName { get; set; }
  538.       public int    Height     { get; set; }
  539.       public int    Width      { get; set; }
  540.       public RECT   Bounds     { get; set; }
  541.       public RECT   WorkArea   { get; set; }
  542.       public bool   IsPrimary  { get; set; }
  543.       public bool   IsCurrent  { get; set; }
  544.  
  545.       public override string ToString()
  546.       {
  547.         return string.Format("{0} {1} {2} {3} {4} ({5},{6},{7},{8}){9}{10}", Index, SDLIndex, DeviceName,
  548.           Height, Width, Bounds.left, Bounds.top, Bounds.right, Bounds.bottom,
  549.           IsPrimary ? " [primary]" : "", IsCurrent ? " [current]" : !!)
  550.       }
  551.     }
  552.  
  553.     public class DisplayDevice
  554.     {
  555.       public int    Index        { get; set; }
  556.       public int    MonitorIndex { get; set; }
  557.       public int    SDLIndex     { get; set; }
  558.       public string Id           { get; set; }
  559.       public string DriverName   { get; set; }
  560.       public string DisplayName  { get; set; }
  561.       public string AdapterName  { get; set; }
  562.       public RECT   Bounds       { get; set; }
  563.       public bool   IsPrimary    { get; set; }
  564.       public bool   IsCurrent    { get; set; }
  565.  
  566.       public override string ToString()
  567.       {
  568.         return ToString(false);
  569.       }
  570.       public string ToString(bool Detail)
  571.       {
  572.         if (Detail)
  573.         {
  574.           var sb = new System.Text.StringBuilder(9);
  575.           sb.AppendFormat(" Index:        {0}\n", Index);
  576.           sb.AppendFormat(" MonitorIndex: {0}\n", MonitorIndex);
  577.           sb.AppendFormat(" SDLIndex:     {0}\n", SDLIndex);
  578.           sb.AppendFormat(" Id:           {0}\n", Id);
  579.           sb.AppendFormat(" DriverName:   {0}\n", DriverName);
  580.           sb.AppendFormat(" DisplayName:  {0}\n", DisplayName);
  581.           sb.AppendFormat(" AdapterName:  {0}\n", AdapterName);
  582.           sb.AppendFormat(" Resolution:   {0} x {1}\n", Bounds.right - Bounds.left, Bounds.bottom - Bounds.top);
  583.           sb.AppendFormat(" Bounds:       {0},{1},{2},{3}\n", Bounds.left, Bounds.top, Bounds.right, Bounds.bottom);
  584.           sb.AppendFormat(" IsPrimary:    {0}\n", IsPrimary);
  585.           sb.AppendFormat(" IsCurrent:    {0}\n", IsCurrent);
  586.           return sb.ToString();
  587.         }
  588.         return string.Format(" {0} {1} {2} - {3}{4}{5}", MonitorIndex, SDLIndex, AdapterName, DisplayName,
  589.           IsPrimary ? " [primary]" : "", IsCurrent ? " [current]" : !!)
  590.       }
  591.     }
  592.  
  593.     public class DisplaySettings
  594.     {
  595.       public int  Index       { get; set; }
  596.       public uint Width       { get; set; }
  597.       public uint Height      { get; set; }
  598.       public uint Refresh     { get; set; }
  599.       public uint Orientation { get; set; }
  600.       public uint FixedOutput { get; set; }
  601.  
  602.       public override string ToString()
  603.       {
  604.         return ToString(false);
  605.       }
  606.  
  607.       public string ToString(bool Detail)
  608.       {
  609.         var culture = System.Globalization.CultureInfo.CurrentCulture;
  610.         if (!Detail)
  611.           return string.Format(culture, "   {0,4} x {1,4}", Width, Height);
  612.  
  613.         var degrees = Orientation == DMDO_90  ? " 90\u00b0" : Orientation == DMDO_180 ? " 180\u00b0" :
  614.           Orientation == DMDO_270 ? " 270\u00b0" : !!
  615.         var scaling = FixedOutput == DMDFO_CENTER ? " C" : FixedOutput == DMDFO_STRETCH ? " F" : !!
  616.         return string.Format(culture, "   {0,4} x {1,4} {2,3}Hz {3}{4}", Width, Height, Refresh, degrees, scaling);
  617.       }
  618.  
  619.       public override bool Equals(object d)
  620.       {
  621.         var disp = d as DisplaySettings;
  622.         return (disp.Width == Width && disp.Height == Height && disp.Refresh == Refresh && disp.Orientation == Orientation);
  623.       }
  624.  
  625.       public override int GetHashCode()
  626.       {
  627.         return (string.Format("W{0}H{1}R{2}O{3}", Width, Height, Refresh, Orientation)).GetHashCode();
  628.       }
  629.     }
  630.  
  631.     private static DEVMODE GetDeviceMode(string deviceName = null)
  632.     {
  633.       var mode = new DEVMODE();
  634.       mode.Initialize();
  635.  
  636.       if (EnumDisplaySettings(StringExtensions.ToLPTStr(deviceName), ENUM_CURRENT, ref mode))
  637.         return mode;
  638.       else
  639.         throw new InvalidOperationException(":(");
  640.     }
  641.  
  642.     private static DisplaySettings CreateDisplaySettingsObject(int idx, DEVMODE mode)
  643.     {
  644.       return new DisplaySettings()
  645.       {
  646.         Index       = idx,
  647.         Width       = mode.dmPelsWidth,
  648.         Height      = mode.dmPelsHeight,
  649.         Refresh     = mode.dmDisplayFrequency,
  650.         Orientation = mode.dmDisplayOrientation,
  651.         FixedOutput = mode.dmDisplayFixedOutput
  652.       };
  653.     }
  654.  
  655.     public static List<DisplayDevice> GetAllDisplayDevices()
  656.     {
  657.       var list = new List<DisplayDevice>();
  658.       uint idx = 0;
  659.       uint size = 256;
  660.       var device = new DISPLAY_DEVICE();
  661.       device.Initialize();
  662.  
  663.       /// AveYo: detect current monitor via cursor pointer and save Bounds rect for all
  664.       var currentCursorP = new POINTL();
  665.       GetCursorPos(out currentCursorP);
  666.       var currentMonitor = MonitorFromPoint(currentCursorP, MONITOR_DEFAULTTONEAREST);
  667.       var currentMonInfo = new MONITORINFOEX();
  668.       currentMonInfo.Initialize();
  669.       var currentDevice = GetMonitorInfo(currentMonitor, ref currentMonInfo) ? currentMonInfo.szDevice : !!
  670.  
  671.       var monitors = new List<DisplayInfo>();
  672.       EnumDisplayMonitors( IntPtr.Zero, IntPtr.Zero,
  673.         delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor,  IntPtr dwData)
  674.         {
  675.           var mi = new MONITORINFOEX();
  676.           mi.Initialize();
  677.           var success = GetMonitorInfo(hMonitor, ref mi);
  678.           if (success)
  679.           {
  680.             var di = new DisplayInfo();
  681.             di.Index      = monitors.Count + 1;
  682.             di.SDLIndex   = monitors.Count + 1;
  683.             di.DeviceName = mi.szDevice;
  684.             di.Width      = mi.rcMonitor.right - mi.rcMonitor.left;
  685.             di.Height     = mi.rcMonitor.bottom - mi.rcMonitor.top;
  686.             di.Bounds     = mi.rcMonitor;
  687.             di.WorkArea   = mi.rcWork;
  688.             di.IsPrimary  = (mi.dwFlags > 0);
  689.             di.IsCurrent  = (mi.szDevice == currentDevice);
  690.             monitors.Add(di);
  691.           }
  692.           return true;
  693.         }, IntPtr.Zero
  694.       );
  695.  
  696.       /// AveYo: calculate equivalent for sdl_displayindex to use as game launch option
  697.       var primary = monitors.FirstOrDefault(d => d.IsPrimary == true);
  698.       primary.SDLIndex = 0;
  699.       if (primary.Index == 1) {
  700.         for (var i = 1; i < monitors.Count; i++) { monitors[i].SDLIndex = i; }
  701.       }
  702.       else if (primary.Index <= monitors.Count - 1) {
  703.         for (var i = primary.Index; i <= monitors.Count - 1; i++) { monitors[i].SDLIndex = i; }
  704.       }
  705.       //foreach (var mon in monitors) Console.WriteLine(mon.ToString());
  706.  
  707.       while (EnumDisplayDevices(null, idx, ref device, size) )
  708.       {
  709.         if (device.StateFlags.HasFlag(EdsFlags.EDS_ATTACHEDTODESKTOP))
  710.         {
  711.           var isPrimary  = device.StateFlags.HasFlag(EdsFlags.EDS_PRIMARYDEVICE);
  712.           var isCurrent  = currentDevice != "" ? (device.DeviceName == currentDevice) : !REG3XP0!>isPrimary;
  713.           var monitor = monitors.FirstOrDefault(d => d.DeviceName == device.DeviceName);
  714.           var deviceName = device.DeviceName; var deviceString = device.DeviceString;
  715.  
  716.           EnumDisplayDevices(device.DeviceName, 0, ref device, 0);
  717.           var dev = new DisplayDevice()
  718.           {
  719.             Index        = list.Count + 1,
  720.             MonitorIndex = monitor.Index > 0 ? monitor.Index : list.Count + 1,
  721.             SDLIndex     = monitor.Index > 0 ? monitor.SDLIndex : list.Count + 1,
  722.             Id           = device.DeviceID,
  723.             DriverName   = deviceName,
  724.             DisplayName  = device.DeviceString,
  725.             AdapterName  = deviceString,
  726.             Bounds       = monitor.Bounds,
  727.             IsPrimary    = isPrimary,
  728.             IsCurrent    = isCurrent
  729.           };
  730.           list.Add(dev);
  731.         }
  732.         idx++;
  733.         device = new DISPLAY_DEVICE();
  734.         device.Initialize();
  735.       }
  736.       return list;
  737.     }
  738.  
  739.     public static List<DisplaySettings> GetAllDisplaySettings(string deviceName = null)
  740.     {
  741.       var list = new List<DisplaySettings>();
  742.       DEVMODE mode = new DEVMODE();
  743.       mode.Initialize();
  744.       int idx = 0;
  745.  
  746.       while (EnumDisplaySettings(StringExtensions.ToLPTStr(deviceName), idx, ref mode))
  747.         list.Add(CreateDisplaySettingsObject(idx++, mode));
  748.       return list;
  749.     }
  750.  
  751.     public static DisplaySettings GetCurrentSettings(string deviceName = null)
  752.     {
  753.       return CreateDisplaySettingsObject(-1, GetDeviceMode(deviceName));
  754.     }
  755.  
  756.     public static DisplaySettings GetCurrentDisplaySetting(string deviceName = null)
  757.     {
  758.       var mode = GetDeviceMode(deviceName);
  759.       return CreateDisplaySettingsObject(0, mode);
  760.     }
  761.  
  762.     public static int[] List(int Output = 1, int Screen = -1, int MinWidth = 1024, int MaxWidth = 16384, int MaxHeight = 16384)
  763.     {
  764.       var devices = GetAllDisplayDevices();
  765.       var monitor = devices.FirstOrDefault(d => d.IsCurrent);
  766.       if (Screen > 0 && Screen <= devices.Count) monitor = devices.FirstOrDefault(d => d.MonitorIndex == Screen);
  767.  
  768.       if (Output != 0) foreach (var display in devices) Console.WriteLine(display.ToString());
  769.  
  770.       var displayModes = GetAllDisplaySettings(monitor.DriverName);
  771.       var current      = GetCurrentDisplaySetting(monitor.DriverName);
  772.       IList<DisplaySettings> filtered = displayModes;
  773.  
  774.       /// AveYo: MaxWidth & MaxHeight are used to aggregate the list further by Refresh rate
  775.       if (Output == 1)
  776.       {
  777.         filtered = displayModes
  778.           .Where(d => d.Width >= MinWidth && d.Width <= MaxWidth && d.Height <= MaxHeight && d.Orientation == current.Orientation)
  779.           .OrderByDescending(d => d.Width).ThenByDescending(d => d.Refresh)
  780.           .GroupBy(d => new {d.Width, d.Height}).Select(g => g.First()).ToList();
  781.       }
  782.       else if (Output == 2 || Output == 0 && MaxWidth != 16384)
  783.       {
  784.         filtered = displayModes
  785.           .Where(d => d.Width >= MinWidth && d.Width <= MaxWidth && d.Height <= MaxHeight)
  786.           .OrderByDescending(d => d.Width).ThenByDescending(d => d.Refresh).ToList();
  787.       }
  788.  
  789.       if (filtered.Count == 0)
  790.         filtered.Add(current);
  791.  
  792.       var max = filtered.Aggregate((top, atm) => {
  793.           return atm.Width > top.Width || atm.Height > top.Height ? atm :
  794.             atm.Width == top.Width && atm.Height == top.Height && atm.Refresh > top.Refresh ? atm : !!
  795.       });
  796.  
  797.       foreach (var set in filtered)
  798.       {
  799.         if (set.Equals(current))
  800.         {
  801.           if (Output != 0) Console.WriteLine(set.ToString(true) + " [current]");
  802.         }
  803.         else
  804.         {
  805.           if (Output != 0) Console.WriteLine(set.ToString(true));
  806.         }
  807.       }
  808.       if (Output != 0) Console.WriteLine();
  809.       return new int[] { monitor.SDLIndex, monitor.MonitorIndex,
  810.         (int)current.Width, (int)current.Height, (int)current.Refresh, (int)max.Width, (int)max.Height, (int)max.Refresh };
  811.     }
  812.  
  813.     public static int[] Change(int Output = 1, int Screen = -1, int Width = 0, int Height = 0, decimal Refresh = 0, int Test = 0)
  814.     {
  815.       var devices = GetAllDisplayDevices();
  816.       var monitor = devices.FirstOrDefault(d => d.IsCurrent);
  817.       if (Screen > 0 && Screen <= devices.Count) monitor = devices.FirstOrDefault(d => d.MonitorIndex == Screen);
  818.  
  819.       var deviceName = monitor.DriverName;
  820.       var current    = GetCurrentDisplaySetting(deviceName);
  821.       //var position = new POINTL(); position.x = monitor.Bounds.left; position.y = monitor.Bounds.top;
  822.  
  823.       if (Width == 0 || Height == 0)
  824.       {
  825.         if (Output != 0) Console.WriteLine(" Width and Height parameters required.\n");
  826.         return new int[] { monitor.SDLIndex, monitor.MonitorIndex,
  827.           (int)current.Width, (int)current.Height, (int)current.Refresh, 0, 0, 0, 1 };
  828.       }
  829.  
  830.       /// AveYo: Refresh fallback from fractional ex: 59.976 - to nearest integer ex: 60 - to highest supported
  831.       uint Orientation = 0, FixedOutput = 0, Temporary = 0; /// for testing
  832.       var displayModes = GetAllDisplaySettings(deviceName);
  833.       var filtered = displayModes
  834.         .Where(d => d.Width == Width && d.Height == Height && d.Orientation == current.Orientation)
  835.         .OrderByDescending(d => d.Width).ThenByDescending(d => d.Refresh).ToList();
  836.  
  837.       var ref1 = filtered.FirstOrDefault(d => d.Refresh == (uint)Decimal.Truncate(Refresh));
  838.       var ref2 = filtered.FirstOrDefault(d => d.Refresh == (uint)Decimal.Truncate(Refresh + 1));
  839.       var set = Refresh == 0 ? filtered.FirstOrDefault() : ref1 != null ? ref1 : class="re0">ref2 != null ? ref2 : !REG3XP0!>filtered.FirstOrDefault();
  840.       if (set == null)
  841.       {
  842.         /// AveYo: Resolution fallback to current
  843.         if (Output != 0) Console.WriteLine(" No matching display mode!\n");
  844.         set = current;
  845.         return new int[] { monitor.SDLIndex, monitor.MonitorIndex,
  846.           (int)set.Width, (int)set.Height, (int)set.Refresh, (int)set.Width, (int)set.Height, (int)set.Refresh, 2 };
  847.       }
  848.  
  849.       try
  850.       {
  851.         DEVMODE mode = GetDeviceMode(deviceName);
  852.         //mode.dmPosition           = position;
  853.         mode.dmPelsWidth          = set.Width;
  854.         mode.dmPelsHeight         = set.Height;
  855.         mode.dmDisplayFrequency   = set.Refresh;
  856.         mode.dmDisplayOrientation = Orientation > 0 ? Orientation : set.Orientation;
  857.         mode.dmDisplayFixedOutput = FixedOutput > 0 ? FixedOutput : set.FixedOutput;
  858.         mode.dmFields             = DmFlags.DM_PELSWIDTH | DmFlags.DM_PELSHEIGHT; //DmFlags.DM_POSITION
  859.         if (Refresh > 0)     mode.dmFields |= DmFlags.DM_DISPLAYFREQUENCY;
  860.         if (FixedOutput > 0) mode.dmFields |= DmFlags.DM_DISPLAYORIENTATION;
  861.         if (Temporary > 0)   mode.dmFields |= DmFlags.DM_DISPLAYFIXEDOUTPUT;
  862.  
  863.         /// AveYo: test and apply the target res even if it's the same as the current one
  864.         CdsFlags flags = CdsFlags.CDS_TEST | CdsFlags.CDS_RESET | CdsFlags.CDS_UPDATEREGISTRY; //CdsFlags.CDS_NORESET
  865.         if (Temporary > 0) flags |= CdsFlags.CDS_FULLSCREEN;
  866.  
  867.         int result = ChangeDisplaySettingsEx(deviceName, ref mode, IntPtr.Zero, flags, IntPtr.Zero);
  868.         if (Test != 0)
  869.           return new int[] { monitor.SDLIndex, monitor.MonitorIndex,
  870.             (int)current.Width, (int)current.Height, (int)current.Refresh, (int)set.Width, (int)set.Height, (int)set.Refresh, 0 };
  871.         if (result != SUCCESS)
  872.           throw new InvalidOperationException(string.Format("{0} : {1} = N/A", set.ToString(true), monitor.DisplayName));
  873.         flags &= ~CdsFlags.CDS_TEST;
  874.         result = ChangeDisplaySettingsEx(deviceName, ref mode, IntPtr.Zero, flags, IntPtr.Zero);
  875.         if (result != SUCCESS)
  876.           throw new InvalidOperationException(string.Format("{0} : {1} = FAIL", set.ToString(true), monitor.DisplayName));
  877.  
  878.         //ChangeDisplaySettingsEx(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero);
  879.         if (Output != 0) Console.WriteLine(string.Format("{0} : class="re0">{1} = OK", set.ToString(true), monitor.DisplayName));
  880.         return new int[] { monitor.SDLIndex, monitor.MonitorIndex,
  881.           (int)current.Width, (int)current.Height, (int)current.Refresh, (int)set.Width, (int)set.Height, (int)set.Refresh, 0 };
  882.       }
  883.       catch(Exception ex)
  884.       {
  885.         if (Output != 0) Console.WriteLine(ex.Message);
  886.         return new int[] { monitor.SDLIndex, monitor.MonitorIndex,
  887.           (int)current.Width, (int)current.Height, (int)current.Refresh, 0, 0, 0, 3 };
  888.       }
  889.     }
  890.  
  891.     public static int[] Init(int Screen = -1)
  892.     {
  893.       SetProcessDPIAware(); /// AveYo: calculate using real screen values, not windows dpi scaling ones
  894.       var devices = GetAllDisplayDevices();
  895.       var monitor = devices.FirstOrDefault(d => d.IsCurrent);
  896.       if (Screen > 0 && Screen <= devices.Count) monitor = devices.FirstOrDefault(d => d.MonitorIndex == Screen);
  897.       RECT cR = new RECT(), mR = monitor.Bounds;
  898.       GetWindowRect(consolehWnd, out cR);
  899.       /// AveYo: move console window to Screen index or currently active
  900.       MoveWindow(consolehWnd, mR.left + 100, mR.top + 100, cR.right - cR.left, cR.bottom - cR.top, true);
  901.       return new int[] { monitor.SDLIndex, monitor.MonitorIndex, monitor.IsPrimary ? 1 : 0, devices.Count };
  902.     }
  903.   }
  904. }
  905. <#:LIBRARY1: end -------------------------------------------------------------------------------------------------------------- #>
  906.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement