From 372b5090cd4073d7168dbc335d5e9863f7f9336c Mon Sep 17 00:00:00 2001 From: Matt Sijbers Date: Sat, 9 May 2026 22:33:52 +0100 Subject: [PATCH] Build-Tweaks: validate every jar before declaring success Adds three structural assertions that run after the zip is built: 1. No backslash separators in any entry name. NeoForge needs "META-INF/neoforge.mods.toml" to be at exactly that forward-slash path; PowerShell 5.1's [ZipFile]::CreateFromDirectory() puts "META-INF\neoforge.mods.toml" instead, which the loader silently rejects. The current build code uses CreateEntry with explicit forward slashes, but this guard fires if anyone reverts to the simpler-looking CreateFromDirectory. 2. META-INF/neoforge.mods.toml exists at the canonical path. Without it, NeoForge skips the jar with a bland "not a valid mod file" warning that's easy to miss in a 500-line game log. 3. The modId declared in the embedded TOML matches the source folder's modId. Catches the case where a tweak folder is renamed but its neoforge.mods.toml isn't updated, which would otherwise ship a jar whose declared identity differs from its filename. Each tweak jar's build line now tags "[validated]" so a casual reader of the log sees that the post-build checks ran. Failure raises a specific exception with the offending entries listed, so the build fails loudly at the source instead of producing a pack that mysteriously doesn't apply its tweaks at runtime. --- scripts/Build-Tweaks.ps1 | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/scripts/Build-Tweaks.ps1 b/scripts/Build-Tweaks.ps1 index 763b1a8..e1833a9 100644 --- a/scripts/Build-Tweaks.ps1 +++ b/scripts/Build-Tweaks.ps1 @@ -122,8 +122,44 @@ foreach ($dir in $tweakDirs) { } } finally { $zip.Dispose() } + # ── Post-build validation ──────────────────────────────────────────────── + # Catch structural problems BEFORE the jar gets bundled into the manifest + # and shipped to clients. NeoForge silently rejects invalid jars with a + # bland "not a valid mod file" warning that's easy to miss in a 500-line + # game log; this fails the build loudly with a specific reason instead. + $verify = [System.IO.Compression.ZipFile]::OpenRead($jarPath) + try { + $names = @($verify.Entries | ForEach-Object { $_.FullName }) + + # 1. No backslashes in entry names. PowerShell 5.1's CreateFromDirectory + # leaks Windows path separators into ZIP entries, which makes + # NeoForge fail to find META-INF/neoforge.mods.toml. We build with + # explicit CreateEntry above, but if anyone "simplifies" the zip + # path back to CreateFromDirectory, this guard fires. + $badSep = $names | Where-Object { $_ -match '\\' } + if ($badSep) { + throw ("$jarName has entries with backslash separators (NeoForge will reject it):`n - " + ($badSep -join "`n - ")) + } + + # 2. META-INF/neoforge.mods.toml must be at the canonical path. + if (-not ($names -contains 'META-INF/neoforge.mods.toml')) { + throw "$jarName is missing META-INF/neoforge.mods.toml at the canonical forward-slash path. Found: $($names -join ', ')" + } + + # 3. modId from the embedded toml must match the jar's filename, so a + # misnamed source folder doesn't ship a jar whose manifest declares + # a different mod (which loads silently under the wrong identity). + $tomlEntry = $verify.GetEntry('META-INF/neoforge.mods.toml') + $reader = New-Object System.IO.StreamReader($tomlEntry.Open()) + try { $tomlContent = $reader.ReadToEnd() } finally { $reader.Dispose() } + $tomlModId = [regex]::Match($tomlContent, '(?m)^\s*modId\s*=\s*"([^"]+)"').Groups[1].Value + if ($tomlModId -ne $meta.ModId) { + throw "$jarName modId mismatch: source folder advertises '$($meta.ModId)' but built jar's neoforge.mods.toml says '$tomlModId'." + } + } finally { $verify.Dispose() } + $size = (Get-Item $jarPath).Length - Write-Host (" built {0,-40} {1,8:N0} bytes" -f $jarName, $size) + Write-Host (" built {0,-40} {1,8:N0} bytes [validated]" -f $jarName, $size) } Write-Host ""