# GeViSet Configuration File Parser and Generator for PowerShell # Supports reading and writing Geutebruck GeViSet .set configuration files class GeViSetParser { # Type markers static [byte] $TYPE_SECTION = 0x00 static [byte] $TYPE_BOOLEAN = 0x01 static [byte] $TYPE_INT64 = 0x02 static [byte] $TYPE_INT32 = 0x04 static [byte] $TYPE_PROPERTY_NAME = 0x07 static [byte] $TYPE_STRING = 0x08 [byte[]] $Data [int] $Position [System.Collections.Specialized.OrderedDictionary] $Config GeViSetParser() { $this.Position = 0 $this.Config = [ordered]@{} } [byte] ReadByte() { if ($this.Position -ge $this.Data.Length) { throw "Unexpected end of file at position $($this.Position)" } $b = $this.Data[$this.Position] $this.Position++ return $b } [byte[]] ReadBytes([int]$count) { if ($this.Position + $count -gt $this.Data.Length) { throw "Unexpected end of file at position $($this.Position)" } $bytes = $this.Data[$this.Position..($this.Position + $count - 1)] $this.Position += $count return $bytes } [uint16] ReadUInt16() { $bytes = $this.ReadBytes(2) return [BitConverter]::ToUInt16($bytes, 0) } [uint32] ReadUInt32() { $bytes = $this.ReadBytes(4) return [BitConverter]::ToUInt32($bytes, 0) } [uint64] ReadUInt64() { $bytes = $this.ReadBytes(8) return [BitConverter]::ToUInt64($bytes, 0) } [string] ReadStringWithLength([int]$lengthBytes) { $length = 0 if ($lengthBytes -eq 1) { $length = $this.ReadByte() } elseif ($lengthBytes -eq 2) { $length = $this.ReadUInt16() } else { throw "Invalid lengthBytes: $lengthBytes" } if ($length -eq 0) { return "" } $stringBytes = $this.ReadBytes($length) try { return [System.Text.Encoding]::UTF8.GetString($stringBytes) } catch { return [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetString($stringBytes) } } [byte] PeekByte() { if ($this.Position -ge $this.Data.Length) { throw "Unexpected end of file at position $($this.Position)" } return $this.Data[$this.Position] } [object] ReadValue([byte]$typeMarker) { switch ($typeMarker) { ([GeViSetParser]::TYPE_BOOLEAN) { return [bool]$this.ReadByte() } ([GeViSetParser]::TYPE_INT32) { return $this.ReadUInt32() } ([GeViSetParser]::TYPE_INT64) { return $this.ReadUInt64() } ([GeViSetParser]::TYPE_STRING) { return $this.ReadStringWithLength(2) } default { throw "Unknown value type: 0x$($typeMarker.ToString('X2')) at position $($this.Position - 1)" } } return $null } [System.Collections.Specialized.OrderedDictionary] ReadSection([string]$sectionName) { $section = [ordered]@{} while ($this.Position -lt $this.Data.Length) { try { $typeMarker = $this.PeekByte() } catch { break } if ($typeMarker -eq [GeViSetParser]::TYPE_SECTION) { # Nested section $this.ReadByte() # Consume type marker $subSectionName = $this.ReadStringWithLength(1) if ([string]::IsNullOrEmpty($subSectionName)) { # Check for end marker if ($this.Position + 3 -lt $this.Data.Length) { $nextFour = $this.Data[$this.Position..($this.Position + 3)] if (($nextFour[0] -eq 0) -and ($nextFour[1] -eq 0) -and ($nextFour[2] -eq 0) -and ($nextFour[3] -eq 0)) { $this.Position += 4 break } } continue } $section[$subSectionName] = $this.ReadSection($subSectionName) } elseif ($typeMarker -eq [GeViSetParser]::TYPE_PROPERTY_NAME) { # Property: name + value $this.ReadByte() # Consume type marker $propName = $this.ReadStringWithLength(1) if ([string]::IsNullOrEmpty($propName)) { continue } try { $valueType = $this.ReadByte() $value = $this.ReadValue($valueType) $section[$propName] = $value } catch { Write-Warning "Error reading property '$propName': $_" break } } else { Write-Warning "Unknown type marker 0x$($typeMarker.ToString('X2')) at position $($this.Position)" $this.ReadByte() # Skip } } return $section } [System.Collections.Specialized.OrderedDictionary] ParseFile([string]$filePath) { Write-Host "Parsing $filePath..." -ForegroundColor Cyan $this.Data = [System.IO.File]::ReadAllBytes($filePath) $this.Position = 0 $this.Config = [ordered]@{} Write-Host "File size: $($this.Data.Length) bytes" -ForegroundColor Gray while ($this.Position -lt $this.Data.Length) { try { $typeMarker = $this.PeekByte() if ($typeMarker -eq [GeViSetParser]::TYPE_SECTION) { $this.ReadByte() # Consume type marker $sectionName = $this.ReadStringWithLength(1) if ([string]::IsNullOrEmpty($sectionName)) { if ($this.Position -ge $this.Data.Length - 10) { break } continue } Write-Host " Reading section: $sectionName" -ForegroundColor Green $this.Config[$sectionName] = $this.ReadSection($sectionName) } else { Write-Warning "Skipping unexpected byte 0x$($typeMarker.ToString('X2')) at position $($this.Position)" $this.ReadByte() } } catch { if ($_ -notmatch "Unexpected end of file") { Write-Warning "Error at position $($this.Position): $_" } break } } Write-Host "Parsing complete. Read $($this.Config.Count) top-level sections." -ForegroundColor Cyan return $this.Config } } class GeViSetGenerator { [System.Collections.Generic.List[byte]] $Buffer GeViSetGenerator() { $this.Buffer = [System.Collections.Generic.List[byte]]::new() } [void] WriteByte([byte]$value) { $this.Buffer.Add($value) } [void] WriteBytes([byte[]]$data) { $this.Buffer.AddRange($data) } [void] WriteUInt16([uint16]$value) { $this.WriteBytes([BitConverter]::GetBytes($value)) } [void] WriteUInt32([uint32]$value) { $this.WriteBytes([BitConverter]::GetBytes($value)) } [void] WriteUInt64([uint64]$value) { $this.WriteBytes([BitConverter]::GetBytes($value)) } [void] WriteStringWithLength([string]$text, [int]$lengthBytes) { $textBytes = [System.Text.Encoding]::UTF8.GetBytes($text) if ($lengthBytes -eq 1) { if ($textBytes.Length -gt 255) { throw "String too long for 1-byte length: $($textBytes.Length) bytes" } $this.WriteByte([byte]$textBytes.Length) } elseif ($lengthBytes -eq 2) { if ($textBytes.Length -gt 65535) { throw "String too long for 2-byte length: $($textBytes.Length) bytes" } $this.WriteUInt16([uint16]$textBytes.Length) } else { throw "Invalid lengthBytes: $lengthBytes" } $this.WriteBytes($textBytes) } [void] WriteValue([object]$value) { if ($value -is [bool]) { $this.WriteByte([GeViSetParser]::TYPE_BOOLEAN) if ($value) { $this.WriteByte([byte]1) } else { $this.WriteByte([byte]0) } } elseif ($value -is [int] -or $value -is [uint32]) { $this.WriteByte([GeViSetParser]::TYPE_INT32) $this.WriteUInt32([uint32]$value) } elseif ($value -is [long] -or $value -is [uint64]) { $this.WriteByte([GeViSetParser]::TYPE_INT64) $this.WriteUInt64([uint64]$value) } elseif ($value -is [string]) { $this.WriteByte([GeViSetParser]::TYPE_STRING) $this.WriteStringWithLength($value, 2) } else { throw "Unsupported value type: $($value.GetType().Name)" } } [void] WriteProperty([string]$name, [object]$value) { $this.WriteByte([GeViSetParser]::TYPE_PROPERTY_NAME) $this.WriteStringWithLength($name, 1) $this.WriteValue($value) } [void] WriteSection([string]$name, [System.Collections.Specialized.OrderedDictionary]$content) { $this.WriteByte([GeViSetParser]::TYPE_SECTION) $this.WriteStringWithLength($name, 1) foreach ($key in $content.Keys) { $value = $content[$key] if ($value -is [System.Collections.Specialized.OrderedDictionary] -or $value -is [hashtable]) { $this.WriteSection($key, $value) } else { $this.WriteProperty($key, $value) } } # Section end marker $this.WriteByte([GeViSetParser]::TYPE_SECTION) $this.WriteByte(0) $this.WriteBytes(@(0x00, 0x00, 0x00, 0x00)) } [void] GenerateFile([System.Collections.Specialized.OrderedDictionary]$config, [string]$filePath) { Write-Host "Generating $filePath..." -ForegroundColor Cyan $this.Buffer = [System.Collections.Generic.List[byte]]::new() foreach ($sectionName in $config.Keys) { Write-Host " Writing section: $sectionName" -ForegroundColor Green $this.WriteSection($sectionName, $config[$sectionName]) } [System.IO.File]::WriteAllBytes($filePath, $this.Buffer.ToArray()) Write-Host "Generated $($this.Buffer.Count) bytes." -ForegroundColor Cyan } } # Export functions for easy use function Read-GeViSetConfig { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Path, [Parameter(Mandatory=$false)] [string]$OutputJson ) $parser = [GeViSetParser]::new() $config = $parser.ParseFile($Path) if ($OutputJson) { $config | ConvertTo-Json -Depth 100 | Set-Content -Path $OutputJson -Encoding UTF8 Write-Host "`nConfiguration saved to $OutputJson" -ForegroundColor Cyan } return $config } function Write-GeViSetConfig { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [System.Collections.Specialized.OrderedDictionary]$Config, [Parameter(Mandatory=$true)] [string]$Path ) $generator = [GeViSetGenerator]::new() $generator.GenerateFile($Config, $Path) } function ConvertFrom-GeViSetJson { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$JsonPath, [Parameter(Mandatory=$true)] [string]$OutputPath ) $json = Get-Content -Path $JsonPath -Raw | ConvertFrom-Json # Convert to OrderedDictionary function ConvertTo-OrderedDict($obj) { $result = [ordered]@{} foreach ($prop in $obj.PSObject.Properties) { if ($prop.Value -is [PSCustomObject]) { $result[$prop.Name] = ConvertTo-OrderedDict $prop.Value } else { $result[$prop.Name] = $prop.Value } } return $result } $config = ConvertTo-OrderedDict $json Write-GeViSetConfig -Config $config -Path $OutputPath } # Example usage Write-Host @" GeViSet Configuration Parser/Generator ====================================== Usage Examples: 1. Parse a .set file to view configuration: `$config = Read-GeViSetConfig -Path "C:\path\to\config.set" 2. Parse and export to JSON: Read-GeViSetConfig -Path "C:\path\to\config.set" -OutputJson "config.json" 3. Modify and save back: `$config = Read-GeViSetConfig -Path "config.set" `$config['Users']['aa']['Password'] = 'newhash' Write-GeViSetConfig -Config `$config -Path "config_modified.set" 4. Convert JSON back to .set: ConvertFrom-GeViSetJson -JsonPath "config.json" -OutputPath "config.set" "@ -ForegroundColor Yellow