#requires -Version 5.1 <# BadWolf 过滤器构建脚本 build.ps1 v1.2 ------------------------------------------------------------------ 用途:把模块目录下的 txt 按文件名排序拼接成 BadWolf-LootFilter.txt, 并完成一致性校验与行号换算。纯 PowerShell,无需安装任何软件。 v1.2 相对 v1.1 的变化 ------------------------------------------------------------------ · 自检新增:孤立 % / 未闭合 % 扫描(半截百分号会吞颜色或漏出明文) 判定:先整体屏蔽合法占位符 %NAME%,剩下的 % 只放行四种写字面百分号的情形 (数值后 40%ED / 右括号后 [4-6]%物理减伤 / 夹在两个占位符之间 / 「物理减伤%」「魔法减伤%」后的百分比示意符) v1.1 相对 v1.0 的变化 ------------------------------------------------------------------ · 模块白名单:只拼 NN-*.txt,其它 txt 一律跳过并黄字警告(不再静默混入) · 备份轮转:build 只保留最近 2 份 .bak,可用 -KeepBackups N 调整 · 临时文件:写盘失败也会清理 .tmp · 自检新增:物品代码白名单(item_codes.txt)/ 死规则 / Formula 引用完整性 · 版本行联动:版本行日期落后于最晚模块改动时间时提示 用法(二选一) ------------------------------------------------------------------ A. 双击 build.bat → 直接校验(窗口不关) B. 命令行: powershell -NoProfile -ExecutionPolicy Bypass -File build.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File build.ps1 build powershell -NoProfile -ExecutionPolicy Bypass -File build.ps1 map 1939 powershell -NoProfile -ExecutionPolicy Bypass -File build.ps1 map 16:139 powershell -NoProfile -ExecutionPolicy Bypass -File build.ps1 export 命令 ------------------------------------------------------------------ check 校验(默认,不写任何文件) build 重建合并版(自动备份 .bak,只保留最近 2 份;-NoBackup 关闭备份) map 行号换算: 1939(合并→模块) 或 16:139(模块→合并) export 导出行号映射表 参数 ------------------------------------------------------------------ -NoBackup build 时不备份原文件 -KeepBackups N 备份保留份数,默认 2 -Strict 有提示项也算不通过(退出码 1) 退出码:0 一致 / 1 有问题 / 2 环境问题 #> param( [Parameter(Position = 0)][string]$Command = 'check', [Parameter(Position = 1)][string]$Arg = '', [switch]$NoBackup, [switch]$Strict, [int]$KeepBackups = 2 ) $ErrorActionPreference = 'Stop' # ---------------------------------------------------------------- 基础路径 $Root = $PSScriptRoot $MergedName = 'BadWolf-LootFilter.txt' $MergedPath = Join-Path $Root $MergedName $MapName = [char]0x884C + [char]0x53F7 + [char]0x6620 + [char]0x5C04 + [char]0x8868 + '.md' function Write-Line([string]$s) { Write-Host $s } function Format-N([long]$n) { return $n.ToString('N0', [System.Globalization.CultureInfo]::InvariantCulture) } function Get-Bar { return ('=' * 74) } function Write-Head([string]$t) { Write-Host ""; Write-Host "[$t]"; Write-Host ('-' * 74) } function Get-Md5Hex([byte[]]$Bytes) { $md5 = [System.Security.Cryptography.MD5]::Create() try { $h = $md5.ComputeHash($Bytes) } finally { $md5.Dispose() } return (-join ($h | ForEach-Object { $_.ToString('x2') })) } # ---------------------------------------------------------------- 模块目录 # 只认「≥10 个 数字-xxx.txt」的目录;多个候选直接报错,绝不猜 function Get-ModuleDir { $cands = @() foreach ($d in @(Get-ChildItem -LiteralPath $Root -Directory -ErrorAction SilentlyContinue)) { $n = @(Get-ChildItem -LiteralPath $d.FullName -Filter '*.txt' -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d{2}-' }).Count if ($n -gt 0) { $cands += [pscustomobject]@{ Path = $d.FullName; Count = $n } } } $good = @($cands | Where-Object { $_.Count -ge 10 } | Sort-Object -Descending Count) if ($good.Count -eq 0) { $tip = '' if ($cands.Count -gt 0) { $tip = ';候选目录(文件数不足):' + (($cands | ForEach-Object { "$($_.Path)($($_.Count)个)" }) -join ',') } throw "找不到模块目录:$Root 下没有包含 ≥10 个 `00-xxx.txt` 形式文件的文件夹$tip" } if ($good.Count -gt 1) { $list = ($good | ForEach-Object { "$($_.Path)($($_.Count) 个)" }) -join ';' throw "检测到多个可能的模块目录,为避免用错目录构建,已停止:$list。请把多余的移走或改名。" } return $good[0].Path } # ---------------------------------------------------------------- 载入模块 # 白名单:只认「NN-名称.txt」;名字里带 副本/备份/copy/bak/(1) 的视为复制品,同样跳过 $script:SkippedModules = @() $CopyNameRe = '副本|备份|复制|copy|bak|backup|\(\d+\)' function Get-Modules([string]$Dir) { $allTxt = @(Get-ChildItem -LiteralPath $Dir -Filter '*.txt' -File) $files = @($allTxt | Where-Object { $_.Name -match '^\d{2}-.*\.txt$' -and $_.Name -notmatch $CopyNameRe } | Sort-Object Name) $script:SkippedModules = @($allTxt | Where-Object { -not ($_.Name -match '^\d{2}-.*\.txt$') -or ($_.Name -match $CopyNameRe) } | Sort-Object Name | ForEach-Object { $_.Name }) $list = New-Object System.Collections.ArrayList $cursor = 1 $strict = [System.Text.UTF8Encoding]::new($false, $true) foreach ($f in $files) { $bytes = [System.IO.File]::ReadAllBytes($f.FullName) $text = [System.Text.Encoding]::UTF8.GetString($bytes) $errors = New-Object System.Collections.ArrayList try { [void]$strict.GetString($bytes) } catch { [void]$errors.Add('不是合法的 UTF-8 编码') } if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { [void]$errors.Add('含 UTF-8 BOM') } $lfOnly = ([regex]::Matches($text, "(?.bak(文件名时间戳可直接按名倒序) function Remove-OldBackup([int]$Keep) { $dir = Split-Path -Parent $MergedPath $pat = (Split-Path -Leaf $MergedPath) + '.*.bak' $all = @(Get-ChildItem -LiteralPath $dir -Filter $pat -File -ErrorAction SilentlyContinue | Sort-Object Name -Descending) if ($all.Count -le $Keep) { return @() } $gone = @($all | Select-Object -Skip $Keep) foreach ($g in $gone) { Remove-Item -LiteralPath $g.FullName -Force -ErrorAction SilentlyContinue } return $gone } # ---------------------------------------------------------------- build function Invoke-Build($Mods) { Write-Line (Get-Bar) Write-Line " BadWolf 过滤器 构建" Write-Line (Get-Bar) $bad = @($Mods | Where-Object { $_.Errors.Count -gt 0 }) if ($bad.Count -gt 0) { Write-Line " [中止] 以下模块存在编码/换行问题:" foreach ($m in $bad) { Write-Line " $($m.Name)" foreach ($e in $m.Errors) { Write-Line " ! $e" } } return 1 } $cat = Join-Bytes $Mods $old = @() if (Test-Path -LiteralPath $MergedPath) { $old = [System.IO.File]::ReadAllBytes($MergedPath) } $same = $false if ($old.Length -eq $cat.Length) { $same = $true for ($i = 0; $i -lt $cat.Length; $i++) { if ($cat[$i] -ne $old[$i]) { $same = $false; break } } } if ($same) { Write-Line " 合并版已是最新,无需重建。" Write-Line "" return Invoke-Check $Mods } if (-not $NoBackup -and (Test-Path -LiteralPath $MergedPath)) { $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' $bak = "$MergedPath.$stamp.bak" Copy-Item -LiteralPath $MergedPath -Destination $bak -Force Write-Line " 已备份 → $(Split-Path -Leaf $bak)" if ($KeepBackups -gt 0) { $gone = @(Remove-OldBackup $KeepBackups) if ($gone.Count -gt 0) { Write-Line " 轮转 删除旧备份 $($gone.Count) 份(保留最近 $KeepBackups 份):$(($gone | ForEach-Object { $_.Name }) -join ', ')" } else { Write-Line " 轮转 现有备份未超过 $KeepBackups 份,无需删除" } } } $tmp = "$MergedPath.tmp" try { [System.IO.File]::WriteAllBytes($tmp, $cat) Move-Item -LiteralPath $tmp -Destination $MergedPath -Force } finally { if (Test-Path -LiteralPath $tmp) { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue } } $new = [System.IO.File]::ReadAllBytes($MergedPath) $lineN = ([regex]::Matches([System.Text.Encoding]::UTF8.GetString($new), "\n")).Count Write-Line " 已写入 $(Format-N $new.Length) B / $(Format-N $lineN) 行 MD5 $(Get-Md5Hex $new)" $okWrite = $false if ($new.Length -eq $cat.Length) { $okWrite = $true for ($i = 0; $i -lt $cat.Length; $i++) { if ($new[$i] -ne $cat[$i]) { $okWrite = $false; break } } } if ($okWrite) { Write-Line " 写入校验通过(逐字节一致)" } else { Write-Line " [错误] 写入校验失败"; return 1 } Write-Line "" return Invoke-Check $Mods } # ---------------------------------------------------------------- map function Invoke-Map($Mods, [string]$Q) { if ($Q -match ':') { $parts = $Q.Split(':') $code = $parts[0]; $ln = 0 if (-not [int]::TryParse($parts[1], [ref]$ln)) { Write-Line "无法解析行号:$Q"; return 1 } $m = $Mods | Where-Object { $_.Code -eq $code -or $_.Name.StartsWith($code) } | Select-Object -First 1 if ($null -eq $m) { Write-Line "找不到模块:$code"; return 1 } if ($ln -lt 1 -or $ln -gt $m.LineCount) { Write-Line "$($m.Name) 只有 $($m.LineCount) 行"; return 1 } $merged = $m.Start + $ln - 1 Write-Line "$($m.Name) 第 $ln 行 → 合并版 L$merged" $lines = Get-Lines $m.Text if ($ln -le $lines.Count) { Write-Line " $($lines[$ln - 1])" } return 0 } $ln = 0 if (-not [int]::TryParse($Q, [ref]$ln)) { Write-Line "无法解析行号:$Q"; return 1 } $m = Find-ModuleOfLine $Mods $ln if ($null -eq $m) { Write-Line "L$ln 超出范围(1-$($Mods[$Mods.Count-1].End))"; return 1 } Write-Line "合并版 L$ln → $($m.Name) 第 $($ln - $m.Start + 1) 行" return 0 } # ---------------------------------------------------------------- export function Invoke-Export($Mods) { $cat = Join-Bytes $Mods $end = $Mods[$Mods.Count - 1].End $sb = New-Object System.Text.StringBuilder [void]$sb.AppendLine('# BadWolf 过滤器行号映射表') [void]$sb.AppendLine('') [void]$sb.AppendLine("- 生成时间:$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") [void]$sb.AppendLine("- 模块数:$($Mods.Count)") [void]$sb.AppendLine("- 模块拼接:$(Format-N $cat.Length) 字节 / $(Format-N $end) 行 / MD5 ``$(Get-Md5Hex $cat)``") [void]$sb.AppendLine('- 换算:`合并行 = 模块起始行 + 模块行 - 1`') [void]$sb.AppendLine('') [void]$sb.AppendLine('| 模块 | 合并版起 | 合并版止 | 行数 | 字节 |') [void]$sb.AppendLine('|---|---:|---:|---:|---:|') foreach ($m in $Mods) { [void]$sb.AppendLine("| $($m.Name) | $($m.Start) | $($m.End) | $($m.LineCount) | $(Format-N $m.ByteLen) |") } [void]$sb.AppendLine("| **合计** | 1 | $end | $end | $(Format-N $cat.Length) |") [void]$sb.AppendLine('') [void]$sb.AppendLine('## 常用锚点') [void]$sb.AppendLine('') foreach ($m in $Mods) { if (@('00','09','16','22','28','37','39') -contains $m.Code) { [void]$sb.AppendLine("- ``$($m.Code)`` $($m.Name) → L$($m.Start)–L$($m.End)") } } $path = Join-Path $Root $MapName [System.IO.File]::WriteAllText($path, $sb.ToString(), (New-Object System.Text.UTF8Encoding($false))) Write-Line "已导出 $path" return 0 } # ---------------------------------------------------------------- 主流程 try { $moduleDir = Get-ModuleDir } catch { Write-Line "[错误] $($_.Exception.Message)" exit 2 } Write-Line (Get-Bar) Write-Line " BadWolf 过滤器构建工具(PowerShell 版)" Write-Line (Get-Bar) Write-Line " 脚本位置 $PSCommandPath" Write-Line " 模块目录 $moduleDir" Write-Line " 合并版 $MergedPath" $mods = Get-Modules $moduleDir if ($mods.Count -eq 0) { Write-Line "[错误] 模块目录里没有 NN-*.txt 模块文件"; exit 2 } if ($script:SkippedModules.Count -gt 0) { Write-Host "" Write-Host " [警告] 以下 txt 不符合「NN-名称.txt」,已跳过(不会进入合并版):" -ForegroundColor Yellow foreach ($n in $script:SkippedModules) { Write-Host " - $n" -ForegroundColor Yellow } Write-Host " 若是正式模块,请改名成 40-xxx.txt 这种形式。" -ForegroundColor Yellow } # 编号必须唯一:两个 16-xxx.txt 会让合并版多出一整段,且顺序取决于文件名,必须人工处理 $dupCodes = @($mods | Group-Object Code | Where-Object { $_.Count -gt 1 }) if ($dupCodes.Count -gt 0) { Write-Line "" Write-Line "[错误] 模块编号重复,已停止(避免把两份内容都拼进合并版):" foreach ($g in $dupCodes) { $names = ($g.Group | ForEach-Object { $_.Name }) -join ' / ' Write-Line (" [$($g.Name)] $names") } Write-Line " 处理办法:把多余的那份移出模块目录,或改成未占用的编号。" exit 2 } switch ($Command.ToLowerInvariant()) { 'build' { exit (Invoke-Build $mods) } 'map' { if ([string]::IsNullOrWhiteSpace($Arg)) { Write-Line "用法:build.bat map 1939 或 build.bat map 16:139"; exit 2 }; exit (Invoke-Map $mods $Arg) } 'export' { exit (Invoke-Export $mods) } 'check' { exit (Invoke-Check $mods) } default { Write-Line "[错误] 未知命令:$Command(可用 check / build / map / export)"; exit 2 } }