1035 lines
36 KiB
PowerShell
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
|
|
}
|