379 lines
13 KiB
PowerShell
379 lines
13 KiB
PowerShell
param(
|
|
[string]$AssetPath = "Unity/Assets/BundleResources/DataAssets/SkillDataAssets.asset",
|
|
[string]$ExportPath = "Unity/Assets/BundleResources/Export/SkillDataAssets.asset",
|
|
[string]$MultilingualPath = "Unity/Assets/BundleResources/Export/Multilingual.asset",
|
|
[string]$MultilingualTxtPath = "Tools/MultilingualTxt.txt",
|
|
[switch]$CheckExport
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$repoRoot = git rev-parse --show-toplevel 2>$null
|
|
if (-not $repoRoot) {
|
|
throw "Not inside a git repository."
|
|
}
|
|
$repoRoot = [System.IO.Path]::GetFullPath($repoRoot.Trim())
|
|
|
|
$requiredSkillTypes = @(
|
|
296, 297, 298, 299,
|
|
300, 301, 302, 303, 304, 305, 306,
|
|
307, 308, 309, 310,
|
|
313, 314, 315, 316, 317, 318, 319, 320, 321, 322,
|
|
327, 328, 329,
|
|
330, 331, 332
|
|
)
|
|
|
|
$leakTokens = @(
|
|
"NotShow:",
|
|
"ShowOnUnitMono:",
|
|
"SkillIcon:",
|
|
"HasShowList:",
|
|
"SkillShowList:",
|
|
"skillPriority:",
|
|
"ReserveOnCarry:",
|
|
"ReserveLeaveCarry:",
|
|
"ReserveGiantUpgrade:",
|
|
"ReserveCommonTransform:",
|
|
"- SkillType:",
|
|
"SkillViewType:"
|
|
)
|
|
|
|
$requiredSkillIconGuids = @{
|
|
296 = "a994762e362347569aaac57eda527bbd"
|
|
297 = "a557b1e216994dbc9aba71daa4b6cd7f"
|
|
298 = "d0c8d1956592444eb96e533d0b0ec728"
|
|
299 = "6b94561a0cb3413499cb61ee8788d4e0"
|
|
330 = "a994762e362347569aaac57eda527bbd"
|
|
331 = "6f47452c0c494c579c2d939697037cd6"
|
|
332 = "4e8a40912d3d450c92996e337cad1067"
|
|
}
|
|
|
|
$knownBadTextTokens = @(
|
|
'\u5E76\u975E\u9644\u8FD1',
|
|
'\u518D\u9644\u8FD1',
|
|
([char]0x947E).ToString(),
|
|
([char]0x934F).ToString(),
|
|
(([char]0x6D93).ToString() + ([char]0x5D86).ToString() + ([char]0x6D86).ToString()),
|
|
([char]0x9286).ToString(),
|
|
([char]0xFFFD).ToString(),
|
|
([char]0x00C3).ToString(),
|
|
([char]0x00C2).ToString()
|
|
)
|
|
|
|
$requiredMultilingualSnippetsById = @{
|
|
21828 = '\u5E76\u4E3A\u9644\u8FD1\u53CB\u65B9'
|
|
21829 = '\u5728\u9644\u8FD1\u751F\u6210'
|
|
21857 = '\u5728\u9644\u8FD1\u751F\u6210'
|
|
}
|
|
|
|
function Resolve-RepoPath([string]$Path) {
|
|
if ([System.IO.Path]::IsPathRooted($Path)) {
|
|
return [System.IO.Path]::GetFullPath($Path)
|
|
}
|
|
return [System.IO.Path]::GetFullPath((Join-Path $repoRoot $Path))
|
|
}
|
|
|
|
function Read-TextUtf8([string]$Path) {
|
|
$fullPath = Resolve-RepoPath $Path
|
|
if (-not (Test-Path -LiteralPath $fullPath)) {
|
|
throw "File not found: $fullPath"
|
|
}
|
|
|
|
return [System.IO.File]::ReadAllText($fullPath, [System.Text.Encoding]::UTF8)
|
|
}
|
|
|
|
function Get-SkillEntries([string]$Path) {
|
|
$fullPath = Resolve-RepoPath $Path
|
|
if (-not (Test-Path -LiteralPath $fullPath)) {
|
|
throw "SkillDataAssets file not found: $fullPath"
|
|
}
|
|
|
|
$lines = Get-Content -LiteralPath $fullPath -Encoding UTF8
|
|
$entries = @()
|
|
$current = $null
|
|
|
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
$line = $lines[$i]
|
|
if ($line -match '^ - SkillType: (\d+)') {
|
|
if ($current) {
|
|
$entries += [pscustomobject]$current
|
|
}
|
|
$current = [ordered]@{
|
|
SkillType = [int]$Matches[1]
|
|
Line = $i + 1
|
|
Text = $line + "`n"
|
|
}
|
|
continue
|
|
}
|
|
|
|
if ($current) {
|
|
if ($line -match '^ SkillViewTypeColorList:') {
|
|
$entries += [pscustomobject]$current
|
|
$current = $null
|
|
break
|
|
}
|
|
|
|
$current.Text += $line + "`n"
|
|
}
|
|
}
|
|
|
|
if ($current) {
|
|
$entries += [pscustomobject]$current
|
|
}
|
|
|
|
return $entries
|
|
}
|
|
|
|
function Get-UnityMetaGuidSet {
|
|
if ($script:UnityMetaGuidSet) {
|
|
return $script:UnityMetaGuidSet
|
|
}
|
|
|
|
$assetsPath = Resolve-RepoPath "Unity/Assets"
|
|
$script:UnityMetaGuidSet = @{}
|
|
Get-ChildItem -LiteralPath $assetsPath -Filter "*.meta" -Recurse -File | ForEach-Object {
|
|
$metaText = [System.IO.File]::ReadAllText($_.FullName, [System.Text.Encoding]::UTF8)
|
|
if ($metaText -match '(?m)^guid: ([0-9a-f]{32})') {
|
|
$script:UnityMetaGuidSet[$Matches[1]] = $_.FullName
|
|
}
|
|
}
|
|
|
|
return $script:UnityMetaGuidSet
|
|
}
|
|
|
|
function Get-SkillFieldValue($Entry, [string]$FieldName) {
|
|
$escapedField = [regex]::Escape($FieldName)
|
|
if ($Entry.Text -match "(?m)^ ${escapedField}: (.*)$") {
|
|
return $Matches[1].Trim()
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Get-SkillIconInfo($Entry) {
|
|
$iconValue = Get-SkillFieldValue $Entry "SkillIcon"
|
|
if ([string]::IsNullOrWhiteSpace($iconValue)) {
|
|
return $null
|
|
}
|
|
|
|
if ($iconValue -match '^\{fileID: ([^,}]+)(?:, guid: ([0-9a-f]{32}), type: ([0-9]+))?\}$') {
|
|
return [pscustomobject]@{
|
|
FileID = $Matches[1]
|
|
Guid = if ($Matches.Count -ge 3) { $Matches[2] } else { "" }
|
|
Type = if ($Matches.Count -ge 4) { $Matches[3] } else { "" }
|
|
}
|
|
}
|
|
|
|
throw "SkillType $($Entry.SkillType) has malformed SkillIcon field near line $($Entry.Line): $iconValue"
|
|
}
|
|
|
|
function Assert-NoSerializedLeakInSkillText([string]$Label, $Entries) {
|
|
foreach ($entry in $Entries) {
|
|
$entryLines = @($entry.Text -split "`n")
|
|
for ($i = 0; $i -lt $entryLines.Count; $i++) {
|
|
$line = $entryLines[$i]
|
|
if ($line -notmatch '^ (SkillName|SkillDesc):[ \t]*(.*)$') {
|
|
continue
|
|
}
|
|
|
|
$fieldName = $Matches[1]
|
|
$fieldText = $Matches[2]
|
|
$j = $i + 1
|
|
while ($j -lt $entryLines.Count) {
|
|
$nextLine = $entryLines[$j]
|
|
if ($nextLine -match '^ [A-Za-z_][A-Za-z0-9_]*:' -or
|
|
$nextLine -match '^ - SkillType:' -or
|
|
$nextLine -match '^ SkillViewTypeColorList:') {
|
|
break
|
|
}
|
|
|
|
$fieldText += "`n" + $nextLine
|
|
$j++
|
|
}
|
|
|
|
foreach ($token in $leakTokens) {
|
|
if ($fieldText.Contains($token)) {
|
|
throw "$Label SkillType $($entry.SkillType) $fieldName contains serialized field token '$token' near line $($entry.Line)."
|
|
}
|
|
}
|
|
|
|
foreach ($token in $knownBadTextTokens) {
|
|
if ($fieldText.Contains($token)) {
|
|
throw "$Label SkillType $($entry.SkillType) $fieldName contains known-bad text token '$token' near line $($entry.Line)."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Assert-SkillIcons([string]$Label, $Entries) {
|
|
$metaGuids = Get-UnityMetaGuidSet
|
|
|
|
foreach ($entry in $Entries) {
|
|
$iconInfo = Get-SkillIconInfo $entry
|
|
if ($null -eq $iconInfo) {
|
|
throw "$Label SkillType $($entry.SkillType) has no SkillIcon field near line $($entry.Line)."
|
|
}
|
|
|
|
if ($iconInfo.FileID -ne "0") {
|
|
if ([string]::IsNullOrWhiteSpace($iconInfo.Guid)) {
|
|
throw "$Label SkillType $($entry.SkillType) has a non-empty SkillIcon fileID but no guid near line $($entry.Line)."
|
|
}
|
|
|
|
if (-not $metaGuids.ContainsKey($iconInfo.Guid)) {
|
|
throw "$Label SkillType $($entry.SkillType) SkillIcon guid $($iconInfo.Guid) has no .meta file under Unity/Assets."
|
|
}
|
|
}
|
|
|
|
$showOnUnitMono = Get-SkillFieldValue $entry "ShowOnUnitMono"
|
|
if ($showOnUnitMono -eq "1" -and ($iconInfo.FileID -eq "0" -or [string]::IsNullOrWhiteSpace($iconInfo.Guid))) {
|
|
throw "$Label SkillType $($entry.SkillType) is ShowOnUnitMono but has an empty SkillIcon near line $($entry.Line)."
|
|
}
|
|
|
|
if ($requiredSkillIconGuids.ContainsKey($entry.SkillType)) {
|
|
$expectedGuid = $requiredSkillIconGuids[$entry.SkillType]
|
|
if ($iconInfo.Guid -ne $expectedGuid) {
|
|
throw "$Label SkillType $($entry.SkillType) SkillIcon guid is $($iconInfo.Guid); expected $expectedGuid."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Assert-SkillIconSync($SourceEntries, $ExportEntries) {
|
|
$sourceByType = @{}
|
|
foreach ($entry in $SourceEntries) {
|
|
$sourceByType[$entry.SkillType] = $entry
|
|
}
|
|
|
|
foreach ($exportEntry in $ExportEntries) {
|
|
if (-not $sourceByType.ContainsKey($exportEntry.SkillType)) {
|
|
continue
|
|
}
|
|
|
|
$sourceIcon = Get-SkillIconInfo $sourceByType[$exportEntry.SkillType]
|
|
$exportIcon = Get-SkillIconInfo $exportEntry
|
|
if ($sourceIcon.FileID -ne $exportIcon.FileID -or $sourceIcon.Guid -ne $exportIcon.Guid) {
|
|
throw "Export SkillType $($exportEntry.SkillType) SkillIcon does not match source. source=$($sourceIcon.FileID)/$($sourceIcon.Guid); export=$($exportIcon.FileID)/$($exportIcon.Guid)."
|
|
}
|
|
}
|
|
}
|
|
|
|
function Assert-SkillDataAsset([string]$Label, [string]$Path) {
|
|
$entries = @(Get-SkillEntries $Path)
|
|
if ($entries.Count -lt 290) {
|
|
throw "$Label SkillDataAssets has only $($entries.Count) skill rows; expected at least 290."
|
|
}
|
|
|
|
$counts = @{}
|
|
foreach ($entry in $entries) {
|
|
if (-not $counts.ContainsKey($entry.SkillType)) {
|
|
$counts[$entry.SkillType] = 0
|
|
}
|
|
$counts[$entry.SkillType]++
|
|
}
|
|
|
|
$duplicates = @($counts.Keys | Where-Object { $counts[$_] -gt 1 } | Sort-Object)
|
|
if ($duplicates.Count -gt 0) {
|
|
throw "$Label SkillDataAssets has duplicate SkillType rows: $($duplicates -join ', ')."
|
|
}
|
|
|
|
$missing = @($requiredSkillTypes | Where-Object { -not $counts.ContainsKey($_) })
|
|
if ($missing.Count -gt 0) {
|
|
throw "$Label SkillDataAssets is missing required SkillType rows: $($missing -join ', ')."
|
|
}
|
|
|
|
Assert-NoSerializedLeakInSkillText $Label $entries
|
|
Assert-SkillIcons $Label $entries
|
|
Write-Host "$Label SkillDataAssets OK: checked $($entries.Count) skill rows."
|
|
return $entries
|
|
}
|
|
|
|
function Get-MultilingualBlocksById([string]$Path) {
|
|
$text = Read-TextUtf8 $Path
|
|
$blocksById = @{}
|
|
$matches = [regex]::Matches($text, '(?ms)^ - ID: (\d+)\r?\n.*?(?=^ - ID: |\z)')
|
|
foreach ($match in $matches) {
|
|
$blocksById[[int]$match.Groups[1].Value] = $match.Value
|
|
}
|
|
|
|
return $blocksById
|
|
}
|
|
|
|
function Assert-ExportMultilingualReferences($ExportEntries, [string]$MultilingualAssetPath) {
|
|
$blocksById = Get-MultilingualBlocksById $MultilingualAssetPath
|
|
|
|
foreach ($entry in $ExportEntries) {
|
|
foreach ($fieldName in @("SkillName", "SkillDesc")) {
|
|
$fieldValue = Get-SkillFieldValue $entry $fieldName
|
|
if ($fieldValue -notmatch '^\d+$') {
|
|
continue
|
|
}
|
|
|
|
$id = [int]$fieldValue
|
|
if (-not $blocksById.ContainsKey($id)) {
|
|
throw "Export SkillType $($entry.SkillType) $fieldName references missing multilingual ID $id."
|
|
}
|
|
|
|
$block = $blocksById[$id]
|
|
if ($block -match '(?m)^ ZH:\s*$') {
|
|
throw "Export SkillType $($entry.SkillType) $fieldName references multilingual ID $id with empty ZH."
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($id in $requiredMultilingualSnippetsById.Keys) {
|
|
if (-not $blocksById.ContainsKey($id)) {
|
|
throw "Export Multilingual.asset is missing required ID $id."
|
|
}
|
|
|
|
$snippet = $requiredMultilingualSnippetsById[$id]
|
|
if (-not $blocksById[$id].Contains($snippet)) {
|
|
throw "Export Multilingual.asset ID $id does not contain required snippet $snippet."
|
|
}
|
|
}
|
|
}
|
|
|
|
function Assert-NoSerializedLeakInTextFile([string]$Label, [string]$Path) {
|
|
$text = Read-TextUtf8 $Path
|
|
foreach ($token in $leakTokens) {
|
|
if ($text.Contains($token)) {
|
|
throw "$Label contains serialized field token '$token': $Path"
|
|
}
|
|
}
|
|
foreach ($token in $knownBadTextTokens) {
|
|
if ($text.Contains($token)) {
|
|
throw "$Label contains known-bad text token '$token': $Path"
|
|
}
|
|
}
|
|
if ($text -match '(^|!@#\$%)21861%\$#@!') {
|
|
throw "$Label still contains polluted multilingual ID 21861: $Path"
|
|
}
|
|
Write-Host "$Label OK: no serialized field leakage detected."
|
|
}
|
|
|
|
$sourceEntries = Assert-SkillDataAsset "Source" $AssetPath
|
|
|
|
if ($CheckExport) {
|
|
$exportEntries = Assert-SkillDataAsset "Export" $ExportPath
|
|
|
|
$sourceSet = @{}
|
|
foreach ($entry in $sourceEntries) {
|
|
$sourceSet[$entry.SkillType] = $true
|
|
}
|
|
|
|
$exportSet = @{}
|
|
foreach ($entry in $exportEntries) {
|
|
$exportSet[$entry.SkillType] = $true
|
|
}
|
|
|
|
$missingInExport = @($sourceSet.Keys | Where-Object { -not $exportSet.ContainsKey($_) } | Sort-Object)
|
|
if ($missingInExport.Count -gt 0) {
|
|
throw "Export SkillDataAssets is stale; missing source SkillType rows: $($missingInExport -join ', ')."
|
|
}
|
|
|
|
Assert-SkillIconSync $sourceEntries $exportEntries
|
|
Assert-ExportMultilingualReferences $exportEntries $MultilingualPath
|
|
Assert-NoSerializedLeakInTextFile "Export Multilingual.asset" $MultilingualPath
|
|
Assert-NoSerializedLeakInTextFile "Tools MultilingualTxt.txt" $MultilingualTxtPath
|
|
}
|