TH1/Tools/ObfuscatedExceptionDecoder.ps1
2026-05-18 16:35:31 +08:00

1035 lines
36 KiB
PowerShell

param(
[string]$MappingPath,
[string]$Text,
[string]$InputPath,
[string]$OutputPath,
[ValidateSet('Annotate', 'Replace')]
[string]$Mode = 'Annotate',
[Alias('AggressiveShortNames')]
[switch]$IncludeOneCharNames,
[switch]$NoGui
)
Set-StrictMode -Version 2.0
$ScriptDirectory = if ([string]::IsNullOrWhiteSpace($PSScriptRoot)) {
Split-Path -Parent $MyInvocation.MyCommand.Path
}
else {
$PSScriptRoot
}
if ([string]::IsNullOrWhiteSpace($MappingPath)) {
$MappingPath = Join-Path $ScriptDirectory 'OPSFile.txt'
}
$script:OpsDecoderUiText = $null
function New-ObjectList {
return New-Object 'System.Collections.Generic.List[object]'
}
function New-StringHashtable {
return New-Object System.Collections.Hashtable -ArgumentList ([System.StringComparer]::Ordinal)
}
function Add-LookupValue {
param(
[hashtable]$Lookup,
[string]$Key,
[object]$Value
)
if ([string]::IsNullOrWhiteSpace($Key)) {
return
}
if (-not $Lookup.ContainsKey($Key)) {
$Lookup[$Key] = New-ObjectList
}
[void]$Lookup[$Key].Add($Value)
}
function Remove-AssemblyPrefix {
param([string]$Key)
$pipeIndex = $Key.IndexOf('|')
if ($pipeIndex -ge 0 -and $pipeIndex -lt ($Key.Length - 1)) {
return $Key.Substring($pipeIndex + 1)
}
return $Key
}
function Get-JsonValue {
param(
[object]$Object,
[string]$Name
)
if ($null -eq $Object) {
return $null
}
if ($Object -is [System.Collections.IDictionary]) {
return $Object[$Name]
}
$property = $Object.PSObject.Properties[$Name]
if ($null -eq $property) {
return $null
}
return $property.Value
}
function Get-JsonEntries {
param([object]$Object)
$entries = New-ObjectList
if ($null -eq $Object) {
return $entries
}
if ($Object -is [System.Collections.IDictionary]) {
foreach ($entry in $Object.GetEnumerator()) {
[void]$entries.Add([pscustomobject]@{
Name = [string]$entry.Key
Value = $entry.Value
})
}
return $entries
}
foreach ($property in $Object.PSObject.Properties) {
[void]$entries.Add([pscustomobject]@{
Name = [string]$property.Name
Value = $property.Value
})
}
return $entries
}
function ConvertFrom-OpsJson {
param([string]$Json)
if ($PSVersionTable.PSEdition -eq 'Desktop') {
Add-Type -AssemblyName System.Web.Extensions
$serializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$serializer.MaxJsonLength = [int]::MaxValue
$serializer.RecursionLimit = 128
return $serializer.DeserializeObject($Json)
}
$convertCommand = Get-Command ConvertFrom-Json
if ($convertCommand.Parameters.ContainsKey('AsHashtable')) {
return ($Json | ConvertFrom-Json -AsHashtable)
}
return ($Json | ConvertFrom-Json)
}
function ConvertFrom-JsonStringLiteral {
param([string]$Text)
if ($null -eq $Text) {
return ''
}
if ($Text.IndexOf('\') -lt 0) {
return $Text
}
return [System.Text.RegularExpressions.Regex]::Unescape($Text)
}
function Get-OpsVersion {
param([string]$Json)
$stringMatch = [regex]::Match($Json, '"Version"\s*:\s*"(?<version>[^"]*)"')
if ($stringMatch.Success) {
return $stringMatch.Groups['version'].Value
}
$match = [regex]::Match($Json, '"Version"\s*:\s*\[(?<version>[^\]]*)\]')
if (-not $match.Success) {
return ''
}
$parts = @(
$match.Groups['version'].Value.Split(',') |
ForEach-Object { $_.Trim().Trim('"') } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
)
return ($parts -join '.')
}
function Get-OpsMappingEntries {
param(
[string]$Json,
[string]$SectionName
)
$entries = New-ObjectList
$sectionPattern = '"' + [regex]::Escape($SectionName) + '"\s*:\s*\{\s*"MemberType"\s*:\s*"[^"]*"\s*,\s*"Mapping"\s*:\s*\{(?<body>.*?)\}\s*\}'
$sectionMatch = [regex]::Match($Json, $sectionPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
if (-not $sectionMatch.Success) {
return $entries
}
$body = $sectionMatch.Groups['body'].Value
$entryRegex = New-Object System.Text.RegularExpressions.Regex('"\s*(?<key>(?:\\.|[^"\\])*)"\s*:\s*"(?<value>(?:\\.|[^"\\])*)"', [System.Text.RegularExpressions.RegexOptions]::Compiled)
foreach ($entryMatch in $entryRegex.Matches($body)) {
[void]$entries.Add([pscustomobject]@{
Name = (ConvertFrom-JsonStringLiteral $entryMatch.Groups['key'].Value)
Value = (ConvertFrom-JsonStringLiteral $entryMatch.Groups['value'].Value)
})
}
return $entries
}
function Get-SymbolInfo {
param(
[string]$SectionName,
[string]$OriginalKey
)
$raw = Remove-AssemblyPrefix $OriginalKey
$info = [ordered]@{
Display = $raw
OriginalType = $null
MemberName = $null
}
if ($SectionName -eq 'Type' -or $SectionName -eq 'Namespace') {
$info.Display = $raw
$info.OriginalType = $raw
return [pscustomobject]$info
}
if ($SectionName -eq 'Method' -or $SectionName -eq 'Property') {
$match = [regex]::Match($raw, '^(?<return>.+?)\s+(?<declaringType>.+)::(?<member>[^\(]+)(?<params>\(.*\))$')
if ($match.Success) {
$declaringType = $match.Groups['declaringType'].Value
$member = $match.Groups['member'].Value
$parameters = $match.Groups['params'].Value
$info.Display = "$declaringType.$member$parameters"
$info.OriginalType = $declaringType
$info.MemberName = $member
return [pscustomobject]$info
}
}
if ($SectionName -eq 'Field' -or $SectionName -eq 'Event') {
$match = [regex]::Match($raw, '^(?<fieldType>.+?)\s+(?<declaringType>.+)::(?<member>.+)$')
if ($match.Success) {
$declaringType = $match.Groups['declaringType'].Value
$member = $match.Groups['member'].Value
$info.Display = "$declaringType.$member"
$info.OriginalType = $declaringType
$info.MemberName = $member
return [pscustomobject]$info
}
}
return [pscustomobject]$info
}
function Get-SectionPriority {
param(
[string]$Section,
[string]$PreviousChar,
[string]$NextChar
)
if ($NextChar -eq '(') {
switch ($Section) {
'Method' { return 0 }
'Property' { return 1 }
'Type' { return 2 }
default { return 5 }
}
}
if ($PreviousChar -eq '.' -or $PreviousChar -eq ':' -or $PreviousChar -eq '/') {
switch ($Section) {
'Method' { return 0 }
'Property' { return 1 }
'Field' { return 2 }
'Event' { return 3 }
'Type' { return 4 }
default { return 5 }
}
}
if ($NextChar -eq ':') {
switch ($Section) {
'Type' { return 0 }
'Method' { return 1 }
'Property' { return 2 }
default { return 5 }
}
}
switch ($Section) {
'Type' { return 0 }
'Method' { return 1 }
'Property' { return 2 }
'Field' { return 3 }
'Event' { return 4 }
default { return 5 }
}
}
function Format-Candidate {
param([object]$Candidate)
return "$(Get-SectionDisplayName $Candidate.Section): $($Candidate.Display)"
}
function Resolve-Candidates {
param(
[object[]]$Candidates,
[string]$PreviousChar,
[string]$NextChar
)
return @(
$Candidates |
Sort-Object `
@{ Expression = { Get-SectionPriority -Section $_.Section -PreviousChar $PreviousChar -NextChar $NextChar } },
@{ Expression = { $_.Display } }
)
}
function Format-Replacement {
param(
[string]$SourceName,
[object[]]$Candidates,
[string]$Mode,
[string]$PreviousChar = '',
[string]$NextChar = ''
)
$ordered = @(Resolve-Candidates -Candidates $Candidates -PreviousChar $PreviousChar -NextChar $NextChar)
if ($ordered.Count -eq 0) {
return $SourceName
}
$primary = $ordered[0]
if ($Mode -eq 'Replace') {
return $primary.Display
}
$label = Format-Candidate $primary
if ($ordered.Count -gt 1) {
$label = "$label; +$($ordered.Count - 1) more"
}
return "$SourceName [$label]"
}
function Add-Hit {
param(
[hashtable]$Hits,
[string]$Name,
[object[]]$Candidates
)
if (-not $Hits.ContainsKey($Name)) {
$Hits[$Name] = [ordered]@{
Count = 0
Candidates = $Candidates
}
}
$Hits[$Name].Count = [int]$Hits[$Name].Count + 1
}
function ConvertTo-ObjectArray {
param([object]$Value)
$items = New-ObjectList
if ($null -eq $Value) {
return $items.ToArray()
}
if ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) {
foreach ($item in $Value) {
[void]$items.Add($item)
}
}
else {
[void]$items.Add($Value)
}
return $items.ToArray()
}
function Get-RegexFromNames {
param([string[]]$Names)
$filteredNames = @(
$Names |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Sort-Object @{ Expression = { $_.Length }; Descending = $true }, @{ Expression = { $_ } } -Unique
)
if ($filteredNames.Count -eq 0) {
return $null
}
$escapedNames = @($filteredNames | ForEach-Object { [regex]::Escape($_) })
$pattern = '(?<![A-Za-z0-9_])(?:' + ($escapedNames -join '|') + ')(?![A-Za-z0-9_])'
return New-Object System.Text.RegularExpressions.Regex($pattern, [System.Text.RegularExpressions.RegexOptions]::Compiled)
}
function New-DecoderIndex {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Mapping file was not found: $Path"
}
$resolvedPath = (Resolve-Path -LiteralPath $Path).Path
$json = Get-Content -LiteralPath $resolvedPath -Raw -Encoding UTF8
$version = Get-OpsVersion -Json $json
$allCandidates = New-ObjectList
$byObfuscatedName = New-StringHashtable
$byCompositeName = New-StringHashtable
$typeOriginalToObfuscated = New-StringHashtable
$sectionCounts = New-StringHashtable
$sectionNames = @('Namespace', 'Type', 'Method', 'Field', 'Property', 'Event')
foreach ($sectionName in $sectionNames) {
$mappingEntries = Get-OpsMappingEntries -Json $json -SectionName $sectionName
if ($mappingEntries.Count -eq 0) {
continue
}
$count = 0
foreach ($property in $mappingEntries) {
$originalKey = [string]$property.Name
$obfuscatedName = [string]$property.Value
if ([string]::IsNullOrWhiteSpace($obfuscatedName)) {
continue
}
$symbolInfo = Get-SymbolInfo -SectionName $sectionName -OriginalKey $originalKey
$candidate = [pscustomobject]@{
Section = $sectionName
Obfuscated = $obfuscatedName
OriginalKey = $originalKey
Display = $symbolInfo.Display
OriginalType = $symbolInfo.OriginalType
MemberName = $symbolInfo.MemberName
}
[void]$allCandidates.Add($candidate)
Add-LookupValue -Lookup $byObfuscatedName -Key $obfuscatedName -Value $candidate
$count++
if ($sectionName -eq 'Type' -and -not [string]::IsNullOrWhiteSpace($symbolInfo.OriginalType)) {
$typeOriginalToObfuscated[$symbolInfo.OriginalType] = $obfuscatedName
}
}
$sectionCounts[$sectionName] = $count
}
foreach ($candidate in $allCandidates) {
if ($candidate.Section -eq 'Type' -or $candidate.Section -eq 'Namespace') {
continue
}
if ([string]::IsNullOrWhiteSpace($candidate.OriginalType)) {
continue
}
if (-not $typeOriginalToObfuscated.ContainsKey($candidate.OriginalType)) {
continue
}
$typeObfuscatedName = [string]$typeOriginalToObfuscated[$candidate.OriginalType]
if ([string]::IsNullOrWhiteSpace($typeObfuscatedName)) {
continue
}
Add-LookupValue -Lookup $byCompositeName -Key "$typeObfuscatedName.$($candidate.Obfuscated)" -Value $candidate
Add-LookupValue -Lookup $byCompositeName -Key "$typeObfuscatedName::$($candidate.Obfuscated)" -Value $candidate
Add-LookupValue -Lookup $byCompositeName -Key "$typeObfuscatedName/$($candidate.Obfuscated)" -Value $candidate
}
$standaloneNames = @($byObfuscatedName.Keys | ForEach-Object { [string]$_ })
$commonShortWords = @(
'am', 'an', 'as', 'at', 'be', 'by', 'do', 'go', 'he', 'if',
'in', 'is', 'it', 'me', 'my', 'no', 'of', 'on', 'or', 'so',
'to', 'up', 'us', 'we'
)
$standaloneDefaultSet = New-StringHashtable
foreach ($entry in $byObfuscatedName.GetEnumerator()) {
$name = [string]$entry.Key
if ($name.Length -ge 3) {
$standaloneDefaultSet[$name] = $true
continue
}
if ($name.Length -eq 2 -and -not $commonShortWords.Contains($name.ToLowerInvariant())) {
$candidates = ConvertTo-ObjectArray -Value $entry.Value
foreach ($candidate in $candidates) {
if ($candidate.Section -eq 'Type') {
$standaloneDefaultSet[$name] = $true
break
}
}
}
}
$tokenRegex = New-Object System.Text.RegularExpressions.Regex('(?<![A-Za-z0-9_])(?<name>[A-Za-z_][A-Za-z0-9_]*)(?![A-Za-z0-9_])')
$compositeRegex = New-Object System.Text.RegularExpressions.Regex('(?<![A-Za-z0-9_])(?<left>[A-Za-z_][A-Za-z0-9_]*)(?<sep>::|\.|/)(?<right>[A-Za-z_][A-Za-z0-9_]*)(?![A-Za-z0-9_])')
return [pscustomobject]@{
MappingPath = $resolvedPath
Version = $version
AllCandidates = $allCandidates
ByObfuscatedName = $byObfuscatedName
ByCompositeName = $byCompositeName
SectionCounts = $sectionCounts
DefaultStandaloneNames = $standaloneDefaultSet
StandaloneRegexDefault = $tokenRegex
StandaloneRegexAll = $tokenRegex
CompositeRegex = $compositeRegex
}
}
function New-SummaryText {
param(
[hashtable]$Hits,
[object]$Index,
[int]$ReplacementCount
)
$lines = New-Object 'System.Collections.Generic.List[string]'
[void]$lines.Add('')
[void]$lines.Add((Get-UiText 'SummaryTitle'))
[void]$lines.Add("$(Get-UiText 'Mapping'): $($Index.MappingPath)")
if (-not [string]::IsNullOrWhiteSpace($Index.Version)) {
[void]$lines.Add("$(Get-UiText 'VersionLabel'): $($Index.Version)")
}
[void]$lines.Add("$(Get-UiText 'HitsLabel'): $ReplacementCount, $(Get-UiText 'UniqueLabel'): $($Hits.Count)")
if ($Hits.Count -eq 0) {
[void]$lines.Add((Get-UiText 'NoMatches'))
return ($lines -join [Environment]::NewLine)
}
[void]$lines.Add('')
foreach ($name in @($Hits.Keys | Sort-Object)) {
$hit = $Hits[$name]
$candidates = @($hit.Candidates)
$ordered = @(Resolve-Candidates -Candidates $candidates -PreviousChar '' -NextChar '')
$primary = if ($ordered.Count -gt 0) { Format-Candidate $ordered[0] } else { "($(Get-UiText 'Unknown'))" }
$suffix = ''
if ($ordered.Count -gt 1) {
$suffix = "$(Get-UiText 'MoreInline')$($ordered.Count - 1)$(Get-UiText 'MoreInlineSuffix')"
}
[void]$lines.Add("$name x$($hit.Count) -> $primary$suffix")
if ($ordered.Count -gt 1) {
foreach ($candidate in @($ordered | Select-Object -Skip 1 -First 5)) {
[void]$lines.Add(" - $(Format-Candidate $candidate)")
}
if ($ordered.Count -gt 6) {
[void]$lines.Add("$(Get-UiText 'MoreLinePrefix')$($ordered.Count - 6)$(Get-UiText 'MoreLineSuffix')")
}
}
}
return ($lines -join [Environment]::NewLine)
}
function Invoke-RegexTransform {
param(
[string]$SourceText,
[System.Text.RegularExpressions.Regex]$Regex,
[scriptblock]$Replacement
)
$builder = New-Object System.Text.StringBuilder
$lastIndex = 0
foreach ($match in $Regex.Matches($SourceText)) {
if ($match.Index -lt $lastIndex) {
continue
}
if ($match.Index -gt $lastIndex) {
[void]$builder.Append($SourceText.Substring($lastIndex, $match.Index - $lastIndex))
}
[void]$builder.Append((& $Replacement $match))
$lastIndex = $match.Index + $match.Length
}
if ($lastIndex -lt $SourceText.Length) {
[void]$builder.Append($SourceText.Substring($lastIndex))
}
return $builder.ToString()
}
function Convert-ObfuscatedText {
param(
[string]$SourceText,
[object]$Index,
[ValidateSet('Annotate', 'Replace')]
[string]$Mode = 'Annotate',
[switch]$IncludeOneCharNames,
[switch]$AppendSummary
)
if ($null -eq $SourceText) {
$SourceText = ''
}
$hits = New-StringHashtable
$stats = @{
ReplacementCount = 0
PlaceholderIndex = 0
}
$result = $SourceText
$placeholders = @{}
$byCompositeName = $Index.ByCompositeName
$byObfuscatedName = $Index.ByObfuscatedName
if ($null -ne $Index.CompositeRegex) {
$result = Invoke-RegexTransform -SourceText $result -Regex $Index.CompositeRegex -Replacement {
param($match)
$name = [string]$match.Value
$candidateList = $byCompositeName.get_Item($name)
$candidates = @(ConvertTo-ObjectArray -Value $candidateList)
if ($candidates.Count -eq 0) {
return $name
}
Add-Hit -Hits $hits -Name $name -Candidates $candidates
$stats.ReplacementCount = [int]$stats.ReplacementCount + 1
$replacement = Format-Replacement -SourceName $name -Candidates $candidates -Mode $Mode
$placeholder = "__OPS_DECODER_PLACEHOLDER_$($stats.PlaceholderIndex)__"
$stats.PlaceholderIndex = [int]$stats.PlaceholderIndex + 1
$placeholders[$placeholder] = $replacement
return $placeholder
}
}
$standaloneRegex = if ($IncludeOneCharNames) { $Index.StandaloneRegexAll } else { $Index.StandaloneRegexDefault }
if ($null -ne $standaloneRegex) {
$sourceForContext = $result
$result = Invoke-RegexTransform -SourceText $result -Regex $standaloneRegex -Replacement {
param($match)
$name = [string]$match.Groups['name'].Value
$shouldDecode = if ($IncludeOneCharNames) {
$byObfuscatedName.ContainsKey($name)
}
else {
$Index.DefaultStandaloneNames.ContainsKey($name)
}
if (-not $shouldDecode) {
return $name
}
$previousChar = ''
$nextChar = ''
if ($match.Index -gt 0) {
$previousChar = $sourceForContext.Substring($match.Index - 1, 1)
}
if (($match.Index + $match.Length) -lt $sourceForContext.Length) {
$nextChar = $sourceForContext.Substring($match.Index + $match.Length, 1)
}
$candidateList = $byObfuscatedName.get_Item($name)
$candidates = @(ConvertTo-ObjectArray -Value $candidateList)
Add-Hit -Hits $hits -Name $name -Candidates $candidates
$stats.ReplacementCount = [int]$stats.ReplacementCount + 1
return Format-Replacement -SourceName $name -Candidates $candidates -Mode $Mode -PreviousChar $previousChar -NextChar $nextChar
}
}
foreach ($placeholder in @($placeholders.Keys)) {
$result = $result.Replace($placeholder, [string]$placeholders[$placeholder])
}
$summary = New-SummaryText -Hits $hits -Index $Index -ReplacementCount ([int]$stats.ReplacementCount)
$fullText = if ($AppendSummary) { $result + [Environment]::NewLine + $summary } else { $result }
return [pscustomobject]@{
Text = $result
Summary = $summary
FullText = $fullText
ReplacementCount = [int]$stats.ReplacementCount
UniqueHitCount = $hits.Count
}
}
function Get-UiText {
param([string]$Key)
if ($null -eq $script:OpsDecoderUiText) {
$script:OpsDecoderUiText = New-StringHashtable
$script:OpsDecoderUiText['Mapping'] = '5pig5bCE'
$script:OpsDecoderUiText['Browse'] = '6YCJ5oup'
$script:OpsDecoderUiText['Reload'] = '6YeN6L29'
$script:OpsDecoderUiText['Annotate'] = '5L+d55WZ5re35reG5ZCNICsg5rOo6YeK'
$script:OpsDecoderUiText['Replace'] = '55u05o6l5pu/5o2i5oiQ5Y6f5ZCN'
$script:OpsDecoderUiText['AggressiveShort'] = '5r+A6L+b55+t5ZCN6L2s5o2i'
$script:OpsDecoderUiText['Convert'] = '6L2s5o2i'
$script:OpsDecoderUiText['InputGroup'] = '6L6T5YWl77ya5ZCO5Y+w5byC5bi45raI5oGvIC8g5aCG5qCI'
$script:OpsDecoderUiText['OutputGroup'] = '6L6T5Ye677ya5Y+N5re35reG57uT5p6c'
$script:OpsDecoderUiText['Paste'] = '5LuO5Ymq6LS05p2/57KY6LS0'
$script:OpsDecoderUiText['Copy'] = '5aSN5Yi257uT5p6c'
$script:OpsDecoderUiText['Save'] = '5L+d5a2Y57uT5p6c'
$script:OpsDecoderUiText['Clear'] = '5riF56m6'
$script:OpsDecoderUiText['Sample'] = '5aGr5YWl56S65L6L'
$script:OpsDecoderUiText['Ready'] = '5bCx57uq'
$script:OpsDecoderUiText['Loading'] = '5q2j5Zyo6K+75Y+W5pig5bCELi4u'
$script:OpsDecoderUiText['LoadedPrefix'] = '5pig5bCE5bey5Yqg6L2977ya'
$script:OpsDecoderUiText['LoadFailedPrefix'] = '5pig5bCE5Yqg6L295aSx6LSl77ya'
$script:OpsDecoderUiText['DonePrefix'] = '6L2s5o2i5a6M5oiQ77ya5ZG95LitIA=='
$script:OpsDecoderUiText['DoneMiddle'] = 'IOWkhO+8jOWUr+S4gOWQjeensCA='
$script:OpsDecoderUiText['DoneSuffix'] = 'IOS4qg=='
$script:OpsDecoderUiText['ConvertFailedPrefix'] = '6L2s5o2i5aSx6LSl77ya'
$script:OpsDecoderUiText['SelectMapping'] = '6YCJ5oupIE9QU0ZpbGUudHh0'
$script:OpsDecoderUiText['Copied'] = '57uT5p6c5bey5aSN5Yi25Yiw5Ymq6LS05p2/'
$script:OpsDecoderUiText['SaveDecoded'] = '5L+d5a2Y5Y+N5re35reG57uT5p6c'
$script:OpsDecoderUiText['SavedPrefix'] = '57uT5p6c5bey5L+d5a2Y77ya'
$script:OpsDecoderUiText['Cleared'] = '5bey5riF56m6'
$script:OpsDecoderUiText['SampleText'] = '5byC5bi45raI5oGvIGZ1OiDmiL/kuLvlub/mkq3lpLHotKUK5byC5bi45raI5oGvIGJsdTog5Y+R6YCB57uZ5oi/5Li75aSx6LSlCmF0IGduLmJsdShSdW50aW1lRGF0YS5NYXBEYXRhLCBSdW50aW1lRGF0YS5QbGF5ZXJEYXRhKQ=='
$script:OpsDecoderUiText['SummaryTitle'] = 'LS0tLSBPUFMg5Y+N5re35reG5pGY6KaBIC0tLS0='
$script:OpsDecoderUiText['VersionLabel'] = '54mI5pys'
$script:OpsDecoderUiText['HitsLabel'] = '5ZG95Lit'
$script:OpsDecoderUiText['UniqueLabel'] = '5ZSv5LiA5ZCN56ew'
$script:OpsDecoderUiText['NoMatches'] = '5rKh5pyJ5Yy56YWN5Yiw5re35reG5ZCN56ew44CC'
$script:OpsDecoderUiText['Unknown'] = '5pyq55+l'
$script:OpsDecoderUiText['MoreInline'] = '77yb5Y+m5pyJIA=='
$script:OpsDecoderUiText['MoreInlineSuffix'] = 'IOS4quWAmemAiQ=='
$script:OpsDecoderUiText['MoreLinePrefix'] = 'ICAtIC4uLiDlj6bmnIkg'
$script:OpsDecoderUiText['MoreLineSuffix'] = 'IOS4qg=='
$script:OpsDecoderUiText['SectionNamespace'] = '5ZG95ZCN56m66Ze0'
$script:OpsDecoderUiText['SectionType'] = '57G75Z6L'
$script:OpsDecoderUiText['SectionMethod'] = '5pa55rOV'
$script:OpsDecoderUiText['SectionField'] = '5a2X5q61'
$script:OpsDecoderUiText['SectionProperty'] = '5bGe5oCn'
$script:OpsDecoderUiText['SectionEvent'] = '5LqL5Lu2'
}
if (-not $script:OpsDecoderUiText.ContainsKey($Key)) {
return $Key
}
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String([string]$script:OpsDecoderUiText[$Key]))
}
function Get-SectionDisplayName {
param([string]$Section)
switch ($Section) {
'Namespace' { return Get-UiText 'SectionNamespace' }
'Type' { return Get-UiText 'SectionType' }
'Method' { return Get-UiText 'SectionMethod' }
'Field' { return Get-UiText 'SectionField' }
'Property' { return Get-UiText 'SectionProperty' }
'Event' { return Get-UiText 'SectionEvent' }
default { return $Section }
}
}
function Show-DecoderGui {
param([string]$InitialMappingPath)
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
$form = New-Object System.Windows.Forms.Form
$form.Text = 'OPS Exception Decoder'
$form.StartPosition = 'CenterScreen'
$form.Width = 1180
$form.Height = 760
$form.MinimumSize = New-Object System.Drawing.Size(940, 600)
$font = New-Object System.Drawing.Font('Microsoft YaHei UI', 9)
$monoFont = New-Object System.Drawing.Font('Consolas', 10)
$form.Font = $font
$main = New-Object System.Windows.Forms.TableLayoutPanel
$main.Dock = 'Fill'
$main.ColumnCount = 1
$main.RowCount = 4
[void]$main.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 44)))
[void]$main.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100)))
[void]$main.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 44)))
[void]$main.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 26)))
$form.Controls.Add($main)
$topPanel = New-Object System.Windows.Forms.TableLayoutPanel
$topPanel.Dock = 'Fill'
$topPanel.ColumnCount = 7
[void]$topPanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 58)))
[void]$topPanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100)))
[void]$topPanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 72)))
[void]$topPanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 72)))
[void]$topPanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 168)))
[void]$topPanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 130)))
[void]$topPanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 120)))
$main.Controls.Add($topPanel, 0, 0)
$mappingLabel = New-Object System.Windows.Forms.Label
$mappingLabel.Text = Get-UiText 'Mapping'
$mappingLabel.TextAlign = 'MiddleRight'
$mappingLabel.Dock = 'Fill'
$topPanel.Controls.Add($mappingLabel, 0, 0)
$mappingTextBox = New-Object System.Windows.Forms.TextBox
$mappingTextBox.Text = $InitialMappingPath
$mappingTextBox.Dock = 'Fill'
$mappingTextBox.Anchor = 'Left,Right'
$topPanel.Controls.Add($mappingTextBox, 1, 0)
$browseButton = New-Object System.Windows.Forms.Button
$browseButton.Text = Get-UiText 'Browse'
$browseButton.Dock = 'Fill'
$topPanel.Controls.Add($browseButton, 2, 0)
$reloadButton = New-Object System.Windows.Forms.Button
$reloadButton.Text = Get-UiText 'Reload'
$reloadButton.Dock = 'Fill'
$topPanel.Controls.Add($reloadButton, 3, 0)
$modeBox = New-Object System.Windows.Forms.ComboBox
$modeBox.DropDownStyle = 'DropDownList'
[void]$modeBox.Items.Add((Get-UiText 'Annotate'))
[void]$modeBox.Items.Add((Get-UiText 'Replace'))
$modeBox.SelectedIndex = 0
$modeBox.Dock = 'Fill'
$topPanel.Controls.Add($modeBox, 4, 0)
$oneCharCheckBox = New-Object System.Windows.Forms.CheckBox
$oneCharCheckBox.Text = Get-UiText 'AggressiveShort'
$oneCharCheckBox.Checked = $false
$oneCharCheckBox.Dock = 'Fill'
$topPanel.Controls.Add($oneCharCheckBox, 5, 0)
$convertButtonTop = New-Object System.Windows.Forms.Button
$convertButtonTop.Text = Get-UiText 'Convert'
$convertButtonTop.Dock = 'Fill'
$topPanel.Controls.Add($convertButtonTop, 6, 0)
$split = New-Object System.Windows.Forms.SplitContainer
$split.Dock = 'Fill'
$split.Orientation = 'Vertical'
$split.SplitterDistance = 545
$main.Controls.Add($split, 0, 1)
$inputGroup = New-Object System.Windows.Forms.GroupBox
$inputGroup.Text = Get-UiText 'InputGroup'
$inputGroup.Dock = 'Fill'
$split.Panel1.Controls.Add($inputGroup)
$inputTextBox = New-Object System.Windows.Forms.TextBox
$inputTextBox.Multiline = $true
$inputTextBox.AcceptsTab = $true
$inputTextBox.AcceptsReturn = $true
$inputTextBox.ScrollBars = 'Both'
$inputTextBox.WordWrap = $false
$inputTextBox.Dock = 'Fill'
$inputTextBox.Font = $monoFont
$inputGroup.Controls.Add($inputTextBox)
$outputGroup = New-Object System.Windows.Forms.GroupBox
$outputGroup.Text = Get-UiText 'OutputGroup'
$outputGroup.Dock = 'Fill'
$split.Panel2.Controls.Add($outputGroup)
$outputTextBox = New-Object System.Windows.Forms.TextBox
$outputTextBox.Multiline = $true
$outputTextBox.AcceptsTab = $true
$outputTextBox.AcceptsReturn = $true
$outputTextBox.ScrollBars = 'Both'
$outputTextBox.WordWrap = $false
$outputTextBox.ReadOnly = $false
$outputTextBox.Dock = 'Fill'
$outputTextBox.Font = $monoFont
$outputGroup.Controls.Add($outputTextBox)
$buttonPanel = New-Object System.Windows.Forms.FlowLayoutPanel
$buttonPanel.Dock = 'Fill'
$buttonPanel.FlowDirection = 'LeftToRight'
$buttonPanel.Padding = New-Object System.Windows.Forms.Padding(8, 5, 8, 5)
$main.Controls.Add($buttonPanel, 0, 2)
$pasteButton = New-Object System.Windows.Forms.Button
$pasteButton.Text = Get-UiText 'Paste'
$pasteButton.Width = 118
$buttonPanel.Controls.Add($pasteButton)
$convertButton = New-Object System.Windows.Forms.Button
$convertButton.Text = Get-UiText 'Convert'
$convertButton.Width = 86
$buttonPanel.Controls.Add($convertButton)
$copyButton = New-Object System.Windows.Forms.Button
$copyButton.Text = Get-UiText 'Copy'
$copyButton.Width = 92
$buttonPanel.Controls.Add($copyButton)
$saveButton = New-Object System.Windows.Forms.Button
$saveButton.Text = Get-UiText 'Save'
$saveButton.Width = 92
$buttonPanel.Controls.Add($saveButton)
$clearButton = New-Object System.Windows.Forms.Button
$clearButton.Text = Get-UiText 'Clear'
$clearButton.Width = 74
$buttonPanel.Controls.Add($clearButton)
$sampleButton = New-Object System.Windows.Forms.Button
$sampleButton.Text = Get-UiText 'Sample'
$sampleButton.Width = 92
$buttonPanel.Controls.Add($sampleButton)
$statusLabel = New-Object System.Windows.Forms.Label
$statusLabel.Text = Get-UiText 'Ready'
$statusLabel.Dock = 'Fill'
$statusLabel.TextAlign = 'MiddleLeft'
$statusLabel.Padding = New-Object System.Windows.Forms.Padding(8, 0, 8, 0)
$main.Controls.Add($statusLabel, 0, 3)
$state = @{
Index = $null
}
$loadMapping = {
try {
$form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
$statusLabel.Text = Get-UiText 'Loading'
$form.Refresh()
$state.Index = New-DecoderIndex -Path $mappingTextBox.Text
$counts = $state.Index.SectionCounts
$statusLabel.Text = "$(Get-UiText 'LoadedPrefix')Type $($counts.Type), Method $($counts.Method), Field $($counts.Field), Property $($counts.Property), Event $($counts.Event)"
}
catch {
$state.Index = $null
$statusLabel.Text = "$(Get-UiText 'LoadFailedPrefix')$($_.Exception.Message)"
[System.Windows.Forms.MessageBox]::Show($form, $_.Exception.Message, 'OPS Exception Decoder', 'OK', 'Error') | Out-Null
}
finally {
$form.Cursor = [System.Windows.Forms.Cursors]::Default
}
}
$runConvert = {
if ($null -eq $state.Index) {
& $loadMapping
}
if ($null -eq $state.Index) {
return
}
try {
$form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
$selectedMode = if ($modeBox.SelectedIndex -eq 1) { 'Replace' } else { 'Annotate' }
$result = Convert-ObfuscatedText -SourceText $inputTextBox.Text -Index $state.Index -Mode $selectedMode -IncludeOneCharNames:$oneCharCheckBox.Checked -AppendSummary
$outputTextBox.Text = $result.FullText
$statusLabel.Text = "$(Get-UiText 'DonePrefix')$($result.ReplacementCount)$(Get-UiText 'DoneMiddle')$($result.UniqueHitCount)$(Get-UiText 'DoneSuffix')"
}
catch {
$statusLabel.Text = "$(Get-UiText 'ConvertFailedPrefix')$($_.Exception.Message)"
[System.Windows.Forms.MessageBox]::Show($form, $_.Exception.Message, 'OPS Exception Decoder', 'OK', 'Error') | Out-Null
}
finally {
$form.Cursor = [System.Windows.Forms.Cursors]::Default
}
}
$browseButton.Add_Click({
$dialog = New-Object System.Windows.Forms.OpenFileDialog
$dialog.Title = Get-UiText 'SelectMapping'
$dialog.Filter = 'OPS mapping (*.txt;*.json)|*.txt;*.json|All files (*.*)|*.*'
if (Test-Path -LiteralPath $mappingTextBox.Text) {
$dialog.InitialDirectory = Split-Path -Parent (Resolve-Path -LiteralPath $mappingTextBox.Text).Path
$dialog.FileName = Split-Path -Leaf $mappingTextBox.Text
}
if ($dialog.ShowDialog($form) -eq 'OK') {
$mappingTextBox.Text = $dialog.FileName
& $loadMapping
}
})
$reloadButton.Add_Click({ & $loadMapping })
$convertButton.Add_Click({ & $runConvert })
$convertButtonTop.Add_Click({ & $runConvert })
$pasteButton.Add_Click({
if ([System.Windows.Forms.Clipboard]::ContainsText()) {
$inputTextBox.Text = [System.Windows.Forms.Clipboard]::GetText()
}
})
$copyButton.Add_Click({
if (-not [string]::IsNullOrEmpty($outputTextBox.Text)) {
[System.Windows.Forms.Clipboard]::SetText($outputTextBox.Text)
$statusLabel.Text = Get-UiText 'Copied'
}
})
$saveButton.Add_Click({
$dialog = New-Object System.Windows.Forms.SaveFileDialog
$dialog.Title = Get-UiText 'SaveDecoded'
$dialog.Filter = 'Text file (*.txt)|*.txt|All files (*.*)|*.*'
$dialog.FileName = 'decoded_exception.txt'
if ($dialog.ShowDialog($form) -eq 'OK') {
[System.IO.File]::WriteAllText($dialog.FileName, $outputTextBox.Text, [System.Text.Encoding]::UTF8)
$statusLabel.Text = "$(Get-UiText 'SavedPrefix')$($dialog.FileName)"
}
})
$clearButton.Add_Click({
$inputTextBox.Clear()
$outputTextBox.Clear()
$statusLabel.Text = Get-UiText 'Cleared'
})
$sampleButton.Add_Click({
$inputTextBox.Text = (Get-UiText 'SampleText')
})
$form.Add_Shown({ & $loadMapping })
[void]$form.ShowDialog()
}
$shouldRunCli = $NoGui -or -not [string]::IsNullOrEmpty($Text) -or -not [string]::IsNullOrEmpty($InputPath) -or -not [string]::IsNullOrEmpty($OutputPath)
if ($shouldRunCli) {
$sourceText = $Text
if (-not [string]::IsNullOrEmpty($InputPath)) {
$sourceText = Get-Content -LiteralPath $InputPath -Raw -Encoding UTF8
}
$index = New-DecoderIndex -Path $MappingPath
$result = Convert-ObfuscatedText -SourceText $sourceText -Index $index -Mode $Mode -IncludeOneCharNames:$IncludeOneCharNames -AppendSummary
if (-not [string]::IsNullOrEmpty($OutputPath)) {
[System.IO.File]::WriteAllText($OutputPath, $result.FullText, [System.Text.Encoding]::UTF8)
}
else {
Write-Output $result.FullText
}
}
else {
Show-DecoderGui -InitialMappingPath $MappingPath
}