Last active
January 2, 2026 15:19
-
-
Save milnak/a37d0b9995a45f62bd9c97b8c89cedc8 to your computer and use it in GitHub Desktop.
Create a ClrMAME .dat file for Visual Pinball VPX tables using data from puplookup.csv.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .DESCRIPTION | |
| Create a ClrMAME .dat file for Visual Pinball VPX tables using data from puplookup.csv. | |
| .NOTES | |
| Table folder structure MUST be in format: | |
| Tables | |
| ├───Whoa Nellie! Big Juicy Melons (Stern 2015) | |
| │ Whoa Nellie! Big Juicy Melons (Stern 2015) UncleWilly 2.1.4 MOD VR.vpx | |
| │ Whoa Nellie! Big Juicy Melons (Stern 2015) UncleWilly 2.1.4 MOD VR.directb2s | |
| Where folder name is in form "GameName (Manufacturer Year)" and | |
| #> | |
| [CmdletBinding()] | |
| param ( | |
| [string]$TablePath = (Resolve-Path 'Tables'), | |
| [string]$OutputXmlFilePath = "$($(Get-Location).Path)\Visual Pinball VPX Tables.xml" | |
| ) | |
| Add-Type -TypeDefinition @' | |
| using System; | |
| using System.Runtime.InteropServices; | |
| public static class NTDLL { | |
| [DllImport("ntdll.dll")] | |
| public static extern UInt32 RtlComputeCrc32(UInt32 InitialCrc, Byte[] Buffer, Int32 Length); | |
| } | |
| '@ | |
| $headerFields = @( | |
| @{ Name = 'author'; Value = '<author>' } | |
| @{ Name = 'category'; Value = '<category>' } | |
| @{ Name = 'clrmamepro'; Value = $null } | |
| @{ Name = 'comment'; Value = '<comment>' } | |
| @{ Name = 'date'; Value = (Get-Date).ToString('MMM d yyyy') } | |
| @{ Name = 'description'; Value = '<description>' } | |
| @{ Name = 'email'; Value = '<email>' } | |
| @{ Name = 'homepage'; Value = '<homepage>' } | |
| @{ Name = 'name'; Value = '<name>' } | |
| @{ Name = 'url'; Value = '<url>' } | |
| @{ Name = 'version'; Value = '<version>' } | |
| ) | |
| # Create a new XML document | |
| $xmlDocument = New-Object Xml.XmlDocument | |
| # version, encoding, standalone | |
| $xmlDecl = $xmlDocument.CreateXmlDeclaration("1.0", 'UTF-8', $null) | |
| $xmlDocument.AppendChild($xmlDecl) | Out-Null | |
| # Parameters: name, publicId, systemId, internalSubset | |
| $docType = $xmlDocument.CreateDocumentType('datafile', '-//Logiqx//DTD ROM Management Datafile//EN', 'http://www.logiqx.com/Dats/datafile.dtd', $null) | |
| $xmlDocument.AppendChild($docType) | Out-Null | |
| # Create the root element | |
| $root = $xmlDocument.CreateElement('datafile') | |
| $xmlDocument.AppendChild($root) | Out-Null | |
| $header = $xmlDocument.CreateElement('header') | |
| foreach ($field in $headerFields) { | |
| $element = $xmlDocument.CreateElement($field.Name) | |
| if ($field.Value) { | |
| $element.InnerText = $field.Value | |
| } | |
| $header.AppendChild($element) | Out-Null | |
| } | |
| $root.AppendChild($header) | Out-Null | |
| $vpxFiles = Get-ChildItem -LiteralPath $TablePath -File -Include '*.vpx', '*.directb2s' -Recurse -Depth 1 | Group-Object DirectoryName | |
| $progressTotal = $vpxFiles.Count | |
| $progressCurrent = 0 | |
| foreach ($directory in $vpxFiles) { | |
| $parentFolderName = Split-Path -Leaf -Path $directory.Name | |
| # e.g. 'Zip-A-Doo (Bally 1970) Teisen 1.0 MOD' | |
| $gameFileBaseName = $directory.Group | Where-Object { $_.Extension -eq '.vpx' } | Select-Object -First 1 -ExpandProperty BaseName | |
| # Use regex to try to guess table, manufacturer and year from filename. | |
| if ($parentFolderName -match '(.+)[ _]?\((.+)(\d{4})\)') { | |
| $metadata = [PSCustomObject]@{ | |
| Table = $matches[1].Trim() | |
| Manufacturer = $matches[2].Trim() | |
| Year = $matches[3].Trim() | |
| } | |
| } | |
| else { | |
| Write-Warning "Could not parse filename '$parentFolderName'. Skipping." | |
| continue | |
| } | |
| $machine = $xmlDocument.CreateElement('machine') | |
| $attribute = $xmlDocument.CreateAttribute('name') | |
| $attribute.Value = $parentFolderName | |
| $machine.Attributes.Append($attribute) | Out-Null | |
| $year = $xmlDocument.CreateElement('year') | |
| $year.InnerText = $metadata.Year | |
| $machine.AppendChild($year) | Out-Null | |
| $manufacturer = $xmlDocument.CreateElement('manufacturer') | |
| $manufacturer.InnerText = $metadata.Manufacturer | |
| $machine.AppendChild($manufacturer) | Out-Null | |
| $description = $xmlDocument.CreateElement('description') | |
| $description.InnerText = $metadata.Table | |
| $machine.AppendChild($description) | Out-Null | |
| foreach ($item in $directory.Group) { | |
| $progressCurrent++ | |
| $percentComplete = [math]::Round(($progressCurrent / $progressTotal) * 100) | |
| Write-Progress ` | |
| -Activity "Processing Tables ($percentComplete%)" ` | |
| -Status $item.Name ` | |
| -PercentComplete $percentComplete | |
| $rom = $xmlDocument.CreateElement('rom') | |
| $attribute = $xmlDocument.CreateAttribute('name') | |
| $attribute.Value = $item.Name | |
| $rom.Attributes.Append($attribute) | Out-Null | |
| $attribute = $xmlDocument.CreateAttribute('size') | |
| $attribute.Value = $item.Length | |
| $rom.Attributes.Append($attribute) | Out-Null | |
| $attribute = $xmlDocument.CreateAttribute('crc') | |
| $fileBytes = [System.IO.File]::ReadAllBytes($item.FullName) | |
| $attribute.Value = '{0:X8}' -f [NTDLL]::RtlComputeCrc32(0, $fileBytes, $fileBytes.Length) | |
| $rom.Attributes.Append($attribute) | Out-Null | |
| $attribute = $xmlDocument.CreateAttribute('md5') | |
| $attribute.Value = ((Get-FileHash -LiteralPath $item.FullName -Algorithm MD5).Hash).ToLower() | |
| $rom.Attributes.Append($attribute) | Out-Null | |
| $attribute = $xmlDocument.CreateAttribute('sha1') | |
| $attribute.Value = ((Get-FileHash -LiteralPath $item.FullName -Algorithm SHA1).Hash).ToLower() | |
| $rom.Attributes.Append($attribute) | Out-Null | |
| $machine.AppendChild($rom) | Out-Null | |
| } | |
| $root.AppendChild($machine) | Out-Null | |
| } | |
| # Save to file | |
| $xmlDocument.Save($OutputXmlFilePath) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment