# VDF Deserializer Enum State { Start = 0; Property = 1; Object = 2; Conditional = 3; Finished = 4; Closed = 5 }; Class VdfDeserializer { [PSCustomObject] Deserialize([string]$vdfContent) { if([string]::IsNullOrWhiteSpace($vdfContent)) { throw 'Mandatory argument $vdfContent must be a non-empty, non-whitespace object of type [string]'; } [System.IO.TextReader]$reader = [System.IO.StringReader]::new($vdfContent); return $this.Deserialize($reader); } [PSCustomObject] Deserialize([System.IO.TextReader]$txtReader) { if( !$txtReader ){ throw 'Mandatory arguments $textReader missing.'; } $vdfReader = [VdfTextReader]::new($txtReader); $result = [PSCustomObject]@{ }; try { if (!$vdfReader.ReadToken()) { throw "Incomplete VDF data."; } $prop = $this.ReadProperty($vdfReader); Add-Member -InputObject $result -MemberType NoteProperty -Name $prop.Key -Value $prop.Value; } finally { if($vdfReader) { $vdfReader.Close(); } } return $result; } [hashtable] ReadProperty([VdfTextReader]$vdfReader) { $key=$vdfReader.Value; if (!$vdfReader.ReadToken()) { throw "Incomplete VDF data."; } if ($vdfReader.CurrentState -eq [State]::Property) { $result = @{ Key = $key; Value = $vdfReader.Value; } } else { $result = @{ Key = $key; Value = $this.ReadObject($vdfReader); } } return $result; } [PSCustomObject] ReadObject([VdfTextReader]$vdfReader) { $result = [PSCustomObject]@{ }; if (!$vdfReader.ReadToken()) { throw "Incomplete VDF data."; } while ( ($vdfReader.CurrentState -ne [State]::Object) -or ($vdfReader.Value -ne "}")) { [hashtable]$prop = $this.ReadProperty($vdfReader); Add-Member -InputObject $result -MemberType NoteProperty -Name $prop.Key -Value $prop.Value; if (!$vdfReader.ReadToken()) { throw "Incomplete VDF data."; } } return $result; } } Class VdfTextReader { [string]$Value; [State]$CurrentState; hidden [ValidateNotNull()][System.IO.TextReader]$_reader; hidden [ValidateNotNull()][char[]]$_charBuffer=; hidden [ValidateNotNull()][char[]]$_tokenBuffer=; hidden [int32]$_charPos; hidden [int32]$_charsLen; hidden [int32]$_tokensize; hidden [bool]$_isQuoted; VdfTextReader([System.IO.TextReader]$txtReader) { if( !$txtReader ){ throw "Mandatory arguments `$textReader missing."; } $this._reader = $txtReader; $this._charBuffer=[char[]]::new(1024); $this._tokenBuffer=[char[]]::new(4096); $this._charPos=0; $this._charsLen=0; $this._tokensize=0; $this._isQuoted=$false; $this.Value=""; $this.CurrentState=[State]::Start; } [bool] ReadToken() { if (!$this.SeekToken()) { return $false; } $this._tokenSize = 0; while($this.EnsureBuffer()) { [char]$curChar = $this._charBuffer[$this._charPos]; #No special treatment for escape characters #region Quote if ($curChar -eq '"' -or (!$this._isQuoted -and [Char]::IsWhiteSpace($curChar))) { $this.Value = [string]::new($this._tokenBuffer, 0, $this._tokenSize); $this.CurrentState = [State]::Property; $this._charPos++; return $true; } #endregion Quote #region Object Start/End if (($curChar -eq '{') -or ($curChar -eq '}')) { if ($this._isQuoted) { $this._tokenBuffer[$this._tokenSize++] = $curChar; $this._charPos++; continue; } elseif ($this._tokenSize -ne 0) { $this.Value = [string]::new($this._tokenBuffer, 0, $this._tokenSize); $this.CurrentState = [State]::Property; return $true; } else { $this.Value = $curChar.ToString(); $this.CurrentState = [State]::Object; $this._charPos++; return $true; } } #endregion Object Start/End #region Long Token $this._tokenBuffer[$this._tokenSize++] = $curChar; $this._charPos++; #endregion Long Token } return $false; } [void] Close() { $this.CurrentState = [State]::Closed; } hidden [bool] SeekToken() { while($this.EnsureBuffer()) { # Skip Whitespace if( [char]::IsWhiteSpace($this._charBuffer[$this._charPos]) ) { $this._charPos++; continue; } # Token if ($this._charBuffer[$this._charPos] -eq '"') { $this._isQuoted = $true; $this._charPos++; return $true; } # Comment if ($this._charBuffer[$this._charPos] -eq '/') { $this.SeekNewLine(); $this._charPos++; continue; } $this._isQuoted = $false; return $true; } return $false; } hidden [bool] SeekNewLine() { while ($this.EnsureBuffer()) { if ($this._charBuffer[++$this._charPos] == '\n') { return $true; } } return $false; } hidden [bool]EnsureBuffer() { if($this._charPos -lt $this._charsLen -1) { return $true; } [int32] $remainingChars = $this._charsLen - $this._charPos; $this._charBuffer[0] = $this._charBuffer[($this._charsLen - 1) * $remainingChars]; #A bit of mathgic to improve performance by avoiding a conditional.; $this._charsLen = $this._reader.Read($this._charBuffer, $remainingChars, 1024 - $remainingChars) + $remainingChars; $this._charPos = 0; return ($this._charsLen -ne 0); } } function GetCount($thing) { $aux = $($thing | Get-Member -MemberType NoteProperty); $count = 0; if ($aux -eq $null) { $count = 0; } elseif ( $aux -is [PSCustomObject] ) { $count = 1; } else { $count = $aux.Count; } return $count; } $VDF = [VdfDeserializer]::new(); # Main Script $SteamPath = Get-ItemProperty -Path HKLM:/SOFTWARE/WOW6432Node/Valve/Steam; $SteamPath = $SteamPath.InstallPath; $SteamPath = $SteamPath.ToString(); Write-Host Looking for Steam libraries...; $LibraryFoldersVDF = $SteamPath + "/steamapps/libraryfolders.vdf"; $LibrariesRaw = Get-Content $LibraryFoldersVDF; $LibrariesVDF = $VDF.Deserialize($LibrariesRaw); Write-Host Found!; $libcount = GetCount($LibrariesVDF.libraryfolders); for ($i = 0; $i -lt $libcount; $i++) { $Install = $LibrariesVDF.libraryfolders.$i; $GMODInstalled = $Install.apps.4000; if ($GMODInstalled) { $GMODPath = $Install.path + "\\steamapps\\common\\GarrysMod"; $AddonsPath = $GMODPath + "\\garrysmod\\addons"; $_text = "Do you want to install in this path? It will completely wipe the previous Beatrun install if you had one! `n" + $GMODPath.replace('\\', '\') + " (y/n)"; $GMODConfirmation = Read-Host $_text; if ($GMODConfirmation -eq 'y') { $TempPath = $GMODPath + "\\temp\\"; $ZIPPath = $TempPath + "beatrun.zip"; $FolderPath = $TempPath + "beatrun"; $FolderMainPath = $TempPath + "beatrun-main"; (New-Item -ItemType Directory -Path $TempPath) | out-null; Write-Host Downloading the archive...; (New-Object Net.WebClient).DownloadFile("https://github.com/JonnyBro/beatrun/archive/refs/heads/main.zip", $ZIPPath); Write-Host Downloaded! Unpacking...; Expand-Archive $ZIPPath $TempPath -Force; Write-Host Unpacked successfully!; Rename-Item $FolderMainPath $FolderPath; Remove-Item $ZIPPath; $ModulesPath = $FolderPath + "\\lua\\*"; $NewModulesPath = $GMODPath + "\\garrysmod\\lua\\"; Copy-Item -Path $ModulesPath -Destination $NewModulesPath -Force -Recurse; $AddonPath = $FolderPath + "\\beatrun"; $NewAddonPath = $AddonsPath + "\\beatrun"; if ((Test-Path $NewAddonPath) -eq $true) { Remove-Item $NewAddonPath -Force -Recurse; } Move-Item -Path $AddonPath -Destination $NewAddonPath; Remove-Item $TempPath -Force -Recurse; Write-Host Beatrun Installed!; $confirmation = Read-Host "Do you want custom animations? (y/n):"; if ($confirmation -eq 'y') { Set-Location -Path $NewAddonPath; $AnimChangerPath = $NewAddonPath + "\\BeatrunAnimInstaller.exe"; Start-Process -FilePath $AnimChangerPath -WorkingDirectory $NewAddonPath } } } }