#requires -Version 5 <# .SYNOPSIS Builds every tweak source folder under ..\pack\tweaks\ into a data-only mod jar and drops it into ..\pack\overrides\mods\. .DESCRIPTION Each subfolder of ..\pack\tweaks\ is treated as one self-contained "data-only" NeoForge mod. Its modId, version, and display name come from the folder's META-INF\neoforge.mods.toml. The script: 1. Reads modId + version from neoforge.mods.toml. 2. Zips the folder contents (data\, META-INF\, pack.mcmeta) into a jar named "-.jar". 3. Removes any older jars for the same modId from the output folder so stale versions don't get bundled into the manifest. Run this before Build-Pack.ps1, or let Build-Pack.ps1 invoke it for you. .PARAMETER TweaksRoot Folder containing tweak source subfolders. Defaults to ..\pack\tweaks\. .PARAMETER OutputRoot Where built jars go. Defaults to ..\pack\overrides\mods\. This path becomes Build-Pack.ps1's -LocalPackSource (one level up). #> [CmdletBinding()] param( [string]$TweaksRoot = "", [string]$OutputRoot = "" ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $here = Split-Path -Parent $MyInvocation.MyCommand.Path if (-not $TweaksRoot) { $TweaksRoot = Join-Path $here '..\pack\tweaks' } if (-not $OutputRoot) { $OutputRoot = Join-Path $here '..\pack\overrides\mods' } if (-not (Test-Path $TweaksRoot)) { Write-Host "No tweaks folder at $TweaksRoot -- nothing to build." return } if (-not (Test-Path $OutputRoot)) { New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null } # Read modId + version out of a neoforge.mods.toml. We do this with a tiny regex # parser instead of a full TOML library because we only need two scalars and we # control the file format. Lines like: modId = "foo" version = "1.2.3" function Get-ModMeta { param([string]$TomlPath) if (-not (Test-Path $TomlPath)) { throw "Missing $TomlPath -- every tweak folder needs META-INF\neoforge.mods.toml." } $content = Get-Content $TomlPath -Raw $modIdMatch = [regex]::Match($content, '(?m)^\s*modId\s*=\s*"([^"]+)"') $versionMatch = [regex]::Match($content, '(?m)^\s*version\s*=\s*"([^"]+)"') if (-not $modIdMatch.Success) { throw "Could not parse modId from $TomlPath" } if (-not $versionMatch.Success) { throw "Could not parse version from $TomlPath" } return [pscustomobject]@{ ModId = $modIdMatch.Groups[1].Value Version = $versionMatch.Groups[1].Value } } Add-Type -AssemblyName System.IO.Compression.FileSystem $tweakDirs = Get-ChildItem -Path $TweaksRoot -Directory if ($tweakDirs.Count -eq 0) { Write-Host "No tweak folders found under $TweaksRoot." return } Write-Host "" Write-Host "Building tweak jars from $TweaksRoot" Write-Host "" foreach ($dir in $tweakDirs) { $tomlPath = Join-Path $dir.FullName 'META-INF\neoforge.mods.toml' $meta = Get-ModMeta -TomlPath $tomlPath $jarName = "$($meta.ModId)-$($meta.Version).jar" $jarPath = Join-Path $OutputRoot $jarName # Wipe stale jars for this modId so old versions don't get bundled into # the manifest alongside the new one. Get-ChildItem -Path $OutputRoot -Filter "$($meta.ModId)-*.jar" -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne $jarName } | ForEach-Object { Write-Host (" removing stale: {0}" -f $_.Name) Remove-Item $_.FullName -Force } if (Test-Path $jarPath) { Remove-Item $jarPath -Force } # NB: PowerShell 5.1's [ZipFile]::CreateFromDirectory() writes Windows- # native path separators (\) into ZIP entry names on Windows, producing # entries like "META-INF\neoforge.mods.toml" instead of the spec-required # "META-INF/neoforge.mods.toml". NeoForge's mod scanner then can't find # the manifest and silently rejects the jar with "not a valid mod file". # We build the archive manually so we control the entry name format. # 'Create' string maps to ZipArchiveMode.Create -- PowerShell 5.1 doesn't # auto-load the enum type, so the literal string form is more portable. $zip = [System.IO.Compression.ZipFile]::Open($jarPath, 'Create') try { $sourceLen = $dir.FullName.Length Get-ChildItem -Path $dir.FullName -Recurse -File | ForEach-Object { $relPath = $_.FullName.Substring($sourceLen).TrimStart('\','/').Replace('\','/') $entry = $zip.CreateEntry($relPath, [System.IO.Compression.CompressionLevel]::Optimal) $entryStream = $entry.Open() try { $bytes = [System.IO.File]::ReadAllBytes($_.FullName) $entryStream.Write($bytes, 0, $bytes.Length) } finally { $entryStream.Dispose() } } } finally { $zip.Dispose() } $size = (Get-Item $jarPath).Length Write-Host (" built {0,-40} {1,8:N0} bytes" -f $jarName, $size) } Write-Host "" Write-Host "Tweak jars are in: $OutputRoot"