Skip to content

Commit 647c661

Browse files
committed
1.0.0
- Initial launch
1 parent 9496681 commit 647c661

13 files changed

+809
-23
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea/
2+
coverage/
3+
.DS_Store

LICENSE

Lines changed: 373 additions & 21 deletions
Large diffs are not rendered by default.

README.adoc

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
= VDF (Valve Data File) parser for PowerShell
2+
:toc:
3+
:toclevels: 5
4+
5+
== Support this tool
6+
7+
link:https://ko-fi.com/E1E3VQUK2[image:https://ko-fi.com/img/githubbutton_sm.svg[Ko-fi]]
8+
9+
== Description
10+
11+
A basic VDF (Valve Data File) parser for PowerShell. Contains functions which you can use in your own PowerShell scripts
12+
to parse VDF files. Outputs `PSObject`.
13+
14+
== How to use
15+
16+
The file link:src/vdf-parser.ps1[vdf-parser.ps1] has a function:
17+
18+
* `ConvertTo-PSObject` - returns the contents of a VDF file as a PSObject
19+
20+
=== Method 1: Copy a function
21+
22+
You can simply copy the function you need from link:src/vdf-parser.ps1[vdf-parser.ps1] into your own script and use it.
23+
24+
[NOTE]
25+
====
26+
Other utilities in this repository may depend on each other. If you want to simply copy them to your script, keep in
27+
mind that you'll need to resolve these dependencies. If you don't want to resolve dependencies yourself, use Method 3
28+
listed below.
29+
====
30+
31+
=== Method 2: Copy the source file
32+
33+
Copy the link:src/vdf-parser.ps1[vdf-parser.ps1] file and attach it to your script with:
34+
35+
[source,powershell]
36+
----
37+
. (Join-Path $PSScriptRoot ./vdf-parser.ps1)
38+
----
39+
40+
=== Method 3: Copy the entire suite
41+
42+
To use this whole suite and all of its features, copy the link:src/[src] directory in your PowerShell project, for
43+
example, to `vdf-parser-powershell` directory (relative to where your script is). You can then include whichever source
44+
file you need with (example):
45+
46+
[source,powershell]
47+
----
48+
. (Join-Path $PSScriptRoot ./vdf-parser-powershell/config-files.ps1)
49+
----
50+
51+
=== Use
52+
53+
You can then use it with:
54+
55+
[source,powershell]
56+
----
57+
$vdfPSObject = ConvertTo-PSObject -vdfContent $pathToYourVdfFile
58+
----
59+
60+
== Other features
61+
62+
This repository contains other useful features. Here is the list of files and functions:
63+
64+
* link:src/config-files.ps1[config-files.ps1] - utilities for various Steam configuration files.
65+
** `Get-LibraryFoldersVdf` - find and return raw contents of Steam's `libraryfolders.vdf` file, or `$null` if it's not
66+
found. Pass this raw contents to a parser function to get a desired data structure.
67+
* link:src/registry.ps1[registry.ps1] - utilities for Steam registry entries.
68+
** `Find-SteamDirectory` - finds and returns your Steam installation directory, or returns `$null` if not found.
69+
70+
== Testing
71+
72+
https://pester.dev/docs/quick-start[Pester 5.6] is used for unit tests in this project. Please note that Windows is shipped
73+
with much older version of Pester. For installation instructions follow the
74+
https://pester.dev/docs/introduction/installation[official guide].
75+
76+
To run the whole test suite (all available tests) with code coverage, use the link:./pester.ps1[`pester.ps1`] script:
77+
78+
[source,shell]
79+
----
80+
pwsh .\pester.ps1
81+
----
82+
83+
Otherwise, you can run individual tests in terminal by following the instructions in
84+
https://pester.dev/docs/quick-start[Pester quick start guide].
85+
86+
=== Testing conventions
87+
88+
- All tests are located in the link:./tests/[tests] directory or its subdirectories.
89+
- All test files are following the naming convention `*.Tests.ps1`.
90+
- Coverage report is available at `./coverage/coverage.xml` (when you run the full test suite, of course);
91+
92+
== External resources
93+
94+
* https://developer.valvesoftware.com/wiki/VDF
95+
* https://developer.valvesoftware.com/wiki/KeyValues
96+
97+
== Support
98+
99+
If you like this tool or find it useful, consider buying me a nice cup of coffee. Coffee fuels open source.
100+
101+
link:https://ko-fi.com/E1E3VQUK2[image:https://ko-fi.com/img/githubbutton_sm.svg[Ko-fi]]
102+
103+
== License
104+
105+
link:LICENSE[Mozilla Public License 2.0] (also available on Mozilla's website at:
106+
https://www.mozilla.org/en-US/MPL/2.0/[MPL 2.0])

README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

pester.ps1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
$config = New-PesterConfiguration
2+
$config.Run.Path = "./tests"
3+
$config.CodeCoverage.Enabled = $true
4+
$config.CodeCoverage.Path = './src'
5+
$config.CodeCoverage.OutputPath = './coverage/coverage.xml'
6+
7+
Invoke-Pester -Configuration $config

src/config-files.ps1

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function Get-LibraryFoldersVdf {
2+
. (Join-Path $PSScriptRoot ./registry.ps1)
3+
4+
$location = "/config/libraryfolders.vdf"
5+
$oldLocation = "/steamapps/libraryfolders.vdf"
6+
7+
$steamDirectory = Find-SteamDirectory
8+
9+
$locationExists = Test-Path -Path "$steamDirectory$location"
10+
if ($locationExists) {
11+
$result = Get-Content -Raw "$steamDirectory$location"
12+
} else {
13+
$locationExists = Test-Path -Path "$steamDirectory$oldLocation"
14+
if ($locationExists) {
15+
$result = Get-Content -Raw "$steamDirectory$oldLocation"
16+
} else {
17+
return $null
18+
}
19+
}
20+
21+
return $result
22+
}

src/registry.ps1

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
function Get-CurrentUserSteamRegistryKeyPath {
2+
return @("HKCU:\Software\Valve\Steam", "SteamPath")
3+
}
4+
5+
function Get-LocalMachineSteamRegistryKeyPath {
6+
return @("HKLM:\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath")
7+
}
8+
9+
function Find-SteamDirectory {
10+
$steamRegistryPath = Get-CurrentUserSteamRegistryKeyPath
11+
$path = $steamRegistryPath[0]
12+
$key = $steamRegistryPath[1]
13+
14+
if ((Test-Path -Path "$path") -and ($null -ne (Get-ItemProperty -Path "$path" -Name "$key" -ErrorAction SilentlyContinue)))
15+
{
16+
return Get-ItemProperty -Path "$path" -Name "$key" | Select-Object -ExpandProperty "$key"
17+
} else {
18+
$steamRegistryPath = Get-LocalMachineSteamRegistryKeyPath
19+
$path = $steamRegistryPath[0]
20+
$key = $steamRegistryPath[1]
21+
22+
if ((Test-Path -Path "$path") -and ($null -ne (Get-ItemProperty -Path "$path" -Name "$key" -ErrorAction SilentlyContinue)))
23+
{
24+
return Get-ItemProperty -Path "$path" -Name "$key" | Select-Object -ExpandProperty "$key"
25+
} else {
26+
return $null
27+
}
28+
}
29+
}

src/vdf-parser.ps1

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
function ConvertTo-PSObject {
2+
param (
3+
[Parameter(Mandatory=$true)]
4+
[string]$vdfContent
5+
)
6+
7+
$lines = $vdfContent -split "`r?`n"
8+
$keysBuffer = [System.Collections.Generic.List[string]]::new()
9+
$valuesBuffer = [System.Collections.Generic.List[PSObject]]::new()
10+
11+
foreach ($line in $lines) {
12+
$trimmedLine = $line.Trim()
13+
14+
if ($trimmedLine -eq "{") {
15+
if ($currentPSObject) {
16+
$valuesBuffer.Add($currentPSObject)
17+
$currentPSObject = [PSCustomObject]@{}
18+
} else {
19+
$currentPSObject = [PSCustomObject]@{}
20+
}
21+
} elseif ($trimmedLine -eq "}") {
22+
if ($keysBuffer.Count -gt 0) {
23+
$key = $keysBuffer[$keysBuffer.Count - 1]
24+
$keysBuffer.RemoveAt($keysBuffer.Count - 1)
25+
}
26+
27+
if ($valuesBuffer.Count -gt 0) {
28+
$parentObject = $valuesBuffer[$valuesBuffer.Count - 1]
29+
$valuesBuffer.RemoveAt($valuesBuffer.Count - 1)
30+
} else {
31+
$parentObject = [PSCustomObject]@{}
32+
}
33+
34+
if ($null -eq $parentObject) {
35+
$parentObject = [PSCustomObject]@{}
36+
}
37+
$parentObject | Add-Member -MemberType NoteProperty -Name $key -Value $currentPSObject
38+
$currentPSObject = $parentObject
39+
} else {
40+
$stringMatches = [regex]::Matches($trimmedLine, '"([^"]*)"')
41+
if ($stringMatches.Count -eq 1) {
42+
$trimmedLine = $trimmedLine.Trim("`"")
43+
$keysBuffer.Add($trimmedLine)
44+
} elseif ($stringMatches.Count -eq 2) {
45+
$currentPSObject | Add-Member -MemberType NoteProperty -Name $stringMatches[0].Groups[1].Value -Value $stringMatches[1].Groups[1].Value
46+
}
47+
}
48+
}
49+
50+
return $currentPSObject
51+
}

tests/config-files.Tests.ps1

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
Describe 'Get-LibraryFoldersVdf' {
2+
BeforeAll {
3+
. "$PSScriptRoot/../src/registry.ps1"
4+
. "$PSScriptRoot/../src/config-files.ps1"
5+
6+
Mock Find-SteamDirectory {
7+
return "TestDrive:\steamdir"
8+
}
9+
}
10+
11+
Context "Only default file is present." {
12+
BeforeAll {
13+
New-Item -ItemType Directory -Force -Path TestDrive:\steamdir\config
14+
$location = "TestDrive:\steamdir\config\libraryfolders.vdf"
15+
$content = "lorem ipsum"
16+
Set-Content $location -Value $content
17+
}
18+
19+
It 'Should return file contents when default location is used.' {
20+
$result = Get-LibraryFoldersVdf
21+
$result | Should -Be "$content`r`n"
22+
}
23+
}
24+
25+
Context "Only old file is present." {
26+
BeforeAll {
27+
New-Item -ItemType Directory -Force -Path TestDrive:\steamdir\steamapps
28+
$location = "TestDrive:\steamdir\steamapps\libraryfolders.vdf"
29+
$content = "lorem ipsum"
30+
Set-Content $location -Value $content
31+
}
32+
33+
It 'Should return file contents when old location is used.' {
34+
Test-Path -Path "TestDrive:\steamdir\config\libraryfolders.vdf" | Should -BeFalse
35+
$result = Get-LibraryFoldersVdf
36+
$result | Should -Be "$content`r`n"
37+
}
38+
}
39+
40+
Context "Both files are present." {
41+
BeforeAll {
42+
New-Item -ItemType Directory -Force -Path TestDrive:\steamdir\config
43+
$location = "TestDrive:\steamdir\config\libraryfolders.vdf"
44+
$content = "config lorem ipsum"
45+
Set-Content $location -Value $content
46+
47+
New-Item -ItemType Directory -Force -Path TestDrive:\steamdir\steamapps
48+
$oldLocation = "TestDrive:\steamdir\steamapps\libraryfolders.vdf"
49+
$oldContent = "steamapps lorem ipsum"
50+
Set-Content $oldLocation -Value $oldContent
51+
}
52+
53+
It "Should return file contents from the config directory if both config and steamapps file exist." {
54+
$result = Get-LibraryFoldersVdf
55+
$result | Should -Be "$content`r`n"
56+
}
57+
}
58+
59+
Context "File does not exist in both locations." {
60+
It 'Should return null if the file was not found.' {
61+
$result = Get-LibraryFoldersVdf
62+
$result | Should -Be $null
63+
}
64+
}
65+
}

tests/registry.Tests.ps1

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
Describe 'Get-CurrentUserSteamRegistryKeyPath' {
2+
BeforeAll {
3+
. "$PSScriptRoot/../src/registry.ps1"
4+
}
5+
6+
It 'Should return registry path and key.' {
7+
$result = Get-CurrentUserSteamRegistryKeyPath
8+
$result | Should -Be @("HKCU:\Software\Valve\Steam", "SteamPath")
9+
}
10+
}
11+
12+
Describe 'Get-LocalMachineSteamRegistryKeyPath' {
13+
BeforeAll {
14+
. "$PSScriptRoot/../src/registry.ps1"
15+
}
16+
17+
It 'Should return registry path and key.' {
18+
$result = Get-LocalMachineSteamRegistryKeyPath
19+
$result | Should -Be @("HKLM:\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath")
20+
}
21+
}
22+
23+
Describe 'Find-SteamDirectory' {
24+
BeforeAll {
25+
. "$PSScriptRoot/../src/registry.ps1"
26+
27+
Mock Get-CurrentUserSteamRegistryKeyPath {
28+
return @("TestRegistry:\Software\Valve\Steam", "SteamPath")
29+
}
30+
31+
Mock Get-LocalMachineSteamRegistryKeyPath {
32+
return @("TestRegistry:\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath")
33+
}
34+
}
35+
36+
Context 'Only HKCU Steam key exists.' {
37+
BeforeAll {
38+
New-Item -Force -Path "TestRegistry:\Software\Valve\Steam"
39+
New-ItemProperty -Path "TestRegistry:\Software\Valve\Steam" -Name "SteamPath" -Value "lorem ipsum"
40+
}
41+
42+
It 'Should read from HKCU if it exists.' {
43+
$result = Find-SteamDirectory
44+
$result | Should -Be "lorem ipsum"
45+
}
46+
}
47+
48+
Context 'Only HKLM Steam key exists.' {
49+
BeforeAll {
50+
New-Item -Force -Path "TestRegistry:\SOFTWARE\WOW6432Node\Valve\Steam"
51+
New-ItemProperty -Path "TestRegistry:\SOFTWARE\WOW6432Node\Valve\Steam" -Name "InstallPath" -Value "lorem ipsum dolor"
52+
}
53+
It 'Should read from HKLM if it exists.' {
54+
$result = Find-SteamDirectory
55+
$result | Should -Be "lorem ipsum dolor"
56+
}
57+
}
58+
59+
Context 'Both HKCU and HKLM Steam keys exist.' {
60+
BeforeAll {
61+
New-Item -Force -Path "TestRegistry:\Software\Valve\Steam"
62+
New-ItemProperty -Path "TestRegistry:\Software\Valve\Steam" -Name "SteamPath" -Value "lorem ipsum"
63+
64+
New-Item -Force -Path "TestRegistry:\SOFTWARE\WOW6432Node\Valve\Steam"
65+
New-ItemProperty -Path "TestRegistry:\SOFTWARE\WOW6432Node\Valve\Steam" -Name "InstallPath" -Value "lorem ipsum dolor"
66+
}
67+
68+
It 'Should read from HKCU if both keys exist' {
69+
$result = Find-SteamDirectory
70+
$result | Should -Be "lorem ipsum"
71+
}
72+
}
73+
74+
Context 'Neither HKCU nor HKLM Steam keys exist.' {
75+
It 'Should return null if neither HKCU nor HKLM Steam key exists.' {
76+
$result = Find-SteamDirectory
77+
$result | Should -Be $null
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)