Skip to content

Instantly share code, notes, and snippets.

@milnak
Last active January 2, 2026 15:19
Show Gist options
  • Select an option

  • Save milnak/a37d0b9995a45f62bd9c97b8c89cedc8 to your computer and use it in GitHub Desktop.

Select an option

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.
<#
.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