Minecraft.psm1
|
function ConvertFrom-GameTicks { <# .SYNOPSIS Converts a number of Minecraft game ticks into the equivalent real-world time span. .DESCRIPTION The ConvertFrom-GameTicks cmdlet converts a number of game ticks into a [timespan] representing how long that is in the real world, at the game's standard rate of 20 ticks per second. .EXAMPLE ConvertFrom-GameTicks -Ticks 24000 Returns a TimeSpan of 00:20:00, i.e. the length of one full Minecraft day/night cycle. .EXAMPLE 24000 | ConvertFrom-GameTicks -Ticks can also be supplied via the pipeline. #> [CmdletBinding()] [OutputType([timespan])] Param( # The number of game ticks to convert. [Parameter(Mandatory, Position = 0, ValueFromPipeline)] [long] $Ticks ) Process { [timespan]::FromSeconds($Ticks / 20) } } function ConvertTo-ChunkCoordinates { <# .SYNOPSIS Converts block coordinates to the coordinates of the chunk that contains them. .DESCRIPTION The ConvertTo-ChunkCoordinates cmdlet converts a pair of block X/Z coordinates into the ChunkX/ ChunkZ coordinates of the 16x16 chunk that contains them, by dividing each value by 16. The result is rounded down to the nearest whole number, matching the game's own chunk indexing. .EXAMPLE ConvertTo-ChunkCoordinates -X 48 -Z -5 Returns the chunk coordinates that contain block X 48, Z -5, i.e. ChunkX 3, ChunkZ -1. .EXAMPLE ConvertTo-ChunkCoordinates 1600 240 X and Z are positional, so they don't need to be named explicitly. .EXAMPLE [pscustomobject]@{ X = 48; Z = -5 } | ConvertTo-ChunkCoordinates X and Z can also be supplied via the pipeline by property name. .EXAMPLE ConvertTo-ChunkCoordinates -X 48 -Z -5 | Test-SlimeChunk -Seed 12345 The returned ChunkX/ChunkZ properties can be piped directly into Test-SlimeChunk. #> [CmdletBinding()] [OutputType([pscustomobject])] Param( # The block X coordinate to convert. [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] [double] $X, # The block Z coordinate to convert. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [double] $Z ) Process { [pscustomobject]@{ ChunkX = [int][Math]::Floor($X / 16) ChunkZ = [int][Math]::Floor($Z / 16) } } } function ConvertTo-ExperienceLevel { <# .SYNOPSIS Converts a total experience point count into the equivalent Minecraft experience level. .DESCRIPTION The ConvertTo-ExperienceLevel cmdlet converts a player's total accumulated experience points into their current experience level, and how far through that level they are (Progress, a value between 0 and 1), using the same formula the game itself uses to calculate the points required for each level. .EXAMPLE ConvertTo-ExperienceLevel -Points 1200 Returns the level and progress equivalent to 1200 total experience points. .EXAMPLE 1200 | ConvertTo-ExperienceLevel -Points can also be supplied via the pipeline. #> [CmdletBinding()] [OutputType([pscustomobject])] Param( # The total accumulated experience points to convert. [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateRange(0, 2147483647)] [int] $Points ) Process { $Level = 0 while ((ConvertTo-ExperiencePoints -Level ($Level + 1)) -le $Points) { $Level++ } $LevelStart = ConvertTo-ExperiencePoints -Level $Level $NextLevelStart = ConvertTo-ExperiencePoints -Level ($Level + 1) [pscustomobject]@{ Level = $Level Progress = [Math]::Round(($Points - $LevelStart) / ($NextLevelStart - $LevelStart), 4) } } } function ConvertTo-ExperiencePoints { <# .SYNOPSIS Converts a Minecraft experience level into the total experience points required to reach it. .DESCRIPTION The ConvertTo-ExperiencePoints cmdlet converts an experience level into the total number of experience points a player must have accumulated to reach the start of that level, using the same piecewise formula the game itself uses (one formula for levels 0-15, another for 16-30, and another for 31 and above). .EXAMPLE ConvertTo-ExperiencePoints -Level 30 Returns 1395, the total experience points required to reach level 30. .EXAMPLE 30 | ConvertTo-ExperiencePoints -Level can also be supplied via the pipeline. #> [CmdletBinding()] [OutputType([int])] Param( # The experience level to convert. [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateRange(0, 2147483647)] [int] $Level ) Process { if ($Level -le 15) { [int]([Math]::Pow($Level, 2) + (6 * $Level)) } elseif ($Level -le 30) { [int]((2.5 * [Math]::Pow($Level, 2)) - (40.5 * $Level) + 360) } else { [int]((4.5 * [Math]::Pow($Level, 2)) - (162.5 * $Level) + 2220) } } } function ConvertTo-GameTicks { <# .SYNOPSIS Converts a real-world time span into the equivalent number of Minecraft game ticks. .DESCRIPTION The ConvertTo-GameTicks cmdlet converts a [timespan] into the number of game ticks it represents, at the game's standard rate of 20 ticks per second. .EXAMPLE ConvertTo-GameTicks -Time (New-TimeSpan -Minutes 20) Returns 24000, the number of ticks in one full Minecraft day/night cycle. .EXAMPLE New-TimeSpan -Seconds 30 | ConvertTo-GameTicks -Time can also be supplied via the pipeline. #> [CmdletBinding()] [OutputType([long])] Param( # The real-world time span to convert. [Parameter(Mandatory, Position = 0, ValueFromPipeline)] [timespan] $Time ) Process { [long][Math]::Round($Time.TotalSeconds * 20) } } function ConvertTo-NetherCoordinates { <# .SYNOPSIS Converts Overworld coordinates to their equivalent Nether coordinates. .DESCRIPTION The ConvertTo-NetherCoordinates cmdlet converts a pair of Overworld X/Z coordinates into the equivalent Nether coordinates, by dividing each value by 8. The result is rounded down to the nearest whole number (block coordinates in Minecraft are always whole numbers), matching the game's own conversion behavior. .EXAMPLE ConvertTo-NetherCoordinates -X 800 -Z -400 Returns the Nether coordinates equivalent to Overworld coordinates X 800, Z -400, i.e. X 100, Z -50. .EXAMPLE ConvertTo-NetherCoordinates 1600 240 X and Z are positional, so they don't need to be named explicitly. .EXAMPLE [pscustomobject]@{ X = 240; Z = 88 } | ConvertTo-NetherCoordinates X and Z can also be supplied via the pipeline by property name, e.g. from an object such as one recording a player's Overworld position. #> [CmdletBinding()] [OutputType([pscustomobject])] Param( # The Overworld X coordinate to convert. [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] [double] $X, # The Overworld Z coordinate to convert. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [double] $Z ) Process { [pscustomobject]@{ X = [int][Math]::Floor($X / 8) Z = [int][Math]::Floor($Z / 8) } } } function ConvertTo-OverworldCoordinates { <# .SYNOPSIS Converts Nether coordinates to their equivalent Overworld coordinates. .DESCRIPTION The ConvertTo-OverworldCoordinates cmdlet converts a pair of Nether X/Z coordinates into the equivalent Overworld coordinates, by multiplying each value by 8, reflecting the Nether's 1:8 scale relative to the Overworld. .EXAMPLE ConvertTo-OverworldCoordinates -X 100 -Z -50 Returns the Overworld coordinates equivalent to Nether coordinates X 100, Z -50, i.e. X 800, Z -400. .EXAMPLE ConvertTo-OverworldCoordinates 200 30 X and Z are positional, so they don't need to be named explicitly. .EXAMPLE [pscustomobject]@{ X = 30; Z = 11 } | ConvertTo-OverworldCoordinates X and Z can also be supplied via the pipeline by property name, e.g. from an object such as one recording a player's Nether position. #> [CmdletBinding()] [OutputType([pscustomobject])] Param( # The Nether X coordinate to convert. [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] [double] $X, # The Nether Z coordinate to convert. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [double] $Z ) Process { [pscustomobject]@{ X = [int]($X * 8) Z = [int]($Z * 8) } } } function ConvertTo-RegionCoordinates { <# .SYNOPSIS Converts block coordinates to the coordinates of the region file that contains them. .DESCRIPTION The ConvertTo-RegionCoordinates cmdlet converts a pair of block X/Z coordinates into the RegionX/RegionZ coordinates of the 32x32 chunk (512x512 block) region that contains them, by dividing each value by 512. The result is rounded down to the nearest whole number, matching the game's own region file naming (e.g. r.0.0.mca). .EXAMPLE ConvertTo-RegionCoordinates -X 600 -Z -5 Returns the region coordinates that contain block X 600, Z -5, i.e. RegionX 1, RegionZ -1. .EXAMPLE ConvertTo-RegionCoordinates 1600 240 X and Z are positional, so they don't need to be named explicitly. .EXAMPLE [pscustomobject]@{ X = 600; Z = -5 } | ConvertTo-RegionCoordinates X and Z can also be supplied via the pipeline by property name. #> [CmdletBinding()] [OutputType([pscustomobject])] Param( # The block X coordinate to convert. [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] [double] $X, # The block Z coordinate to convert. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [double] $Z ) Process { [pscustomobject]@{ RegionX = [int][Math]::Floor($X / 512) RegionZ = [int][Math]::Floor($Z / 512) } } } function Get-Distance { <# .SYNOPSIS Calculates the horizontal block distance between two sets of coordinates. .DESCRIPTION The Get-Distance cmdlet calculates the straight-line distance between two X/Z coordinate pairs, such as those returned by New-Coordinates, ConvertTo-NetherCoordinates or ConvertTo-OverworldCoordinates. .EXAMPLE Get-Distance -From (New-Coordinates -X 0 -Z 0) -To (New-Coordinates -X 300 -Z 400) Returns 500, the distance between the two points. .EXAMPLE New-Coordinates -X 0 -Z 0 | Get-Distance -To (New-Coordinates -X 300 -Z 400) -From can also be supplied via the pipeline. #> [CmdletBinding()] [OutputType([double])] Param( # The coordinates to measure from. [Parameter(Mandatory, Position = 0, ValueFromPipeline)] [pscustomobject] $From, # The coordinates to measure to. [Parameter(Mandatory, Position = 1)] [pscustomobject] $To ) Process { [Math]::Round([Math]::Sqrt([Math]::Pow($To.X - $From.X, 2) + [Math]::Pow($To.Z - $From.Z, 2)), 2) } } function Get-MinecraftDefault { <# .SYNOPSIS Gets the default values previously stored by Set-MinecraftDefault. .DESCRIPTION The Get-MinecraftDefault cmdlet reads the settings stored by Set-MinecraftDefault from $HOME/.minecraft/defaults.json, e.g. a default Seed or Edition that Test-SlimeChunk falls back to when -Seed or -Edition isn't supplied explicitly. Returns $null (or, with no -Name, an empty object) if no defaults have been stored yet. .EXAMPLE Get-MinecraftDefault Returns all currently stored default values. .EXAMPLE Get-MinecraftDefault -Name Seed Returns just the stored default Seed value, or $null if one hasn't been set. #> [CmdletBinding()] [OutputType([pscustomobject])] Param( # The name of a specific default value to return. If omitted, all stored defaults are returned. [Parameter(Position = 0)] [string] $Name ) $Path = Join-Path -Path $HOME -ChildPath '.minecraft/defaults.json' $Defaults = if (Test-Path -Path $Path) { Get-Content -Path $Path -Raw | ConvertFrom-Json } else { [pscustomobject]@{} } if ($Name) { $Defaults.$Name } else { $Defaults } } function New-Coordinates { <# .SYNOPSIS Creates a coordinates object with X and Z properties. .DESCRIPTION The New-Coordinates cmdlet returns a PowerShell object with X and Z properties, for use as input to the other coordinate conversion cmdlets in this module, or anywhere else a simple X/Z position is needed. .EXAMPLE New-Coordinates -X 800 -Z -400 Returns a coordinates object with X 800, Z -400. .EXAMPLE New-Coordinates 1600 240 X and Z are positional, so they don't need to be named explicitly. .EXAMPLE New-Coordinates -X 800 -Z -400 | ConvertTo-NetherCoordinates The returned object can be passed directly into ConvertTo-NetherCoordinates or ConvertTo-OverworldCoordinates via the pipeline. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Creates an in-memory object only; there is no system state to confirm changing.')] [CmdletBinding()] [OutputType([pscustomobject])] Param( # The X coordinate. [Parameter(Mandatory, Position = 0)] [double] $X, # The Z coordinate. [Parameter(Mandatory, Position = 1)] [double] $Z ) [pscustomobject]@{ X = $X Z = $Z } } function Set-MinecraftDefault { <# .SYNOPSIS Stores default values that other cmdlets in this module fall back to when not supplied explicitly. .DESCRIPTION The Set-MinecraftDefault cmdlet saves settings to $HOME/.minecraft/defaults.json so they don't need to be supplied on every call. Currently supports: - A default Seed, which Test-SlimeChunk uses automatically when -Seed isn't supplied explicitly (Java Edition only). - A default Edition, which Test-SlimeChunk uses automatically when -Edition isn't supplied explicitly. At least one of -Seed or -Edition must be supplied. Either can be set independently -- supplying just one leaves any previously stored value for the other untouched. .EXAMPLE Set-MinecraftDefault -Seed 12345 Stores 12345 as the default Seed, so e.g. Test-SlimeChunk -X 48 -Z -32 uses it automatically. .EXAMPLE Set-MinecraftDefault -Edition Bedrock Stores Bedrock as the default Edition, so Test-SlimeChunk uses it automatically without needing -Edition Bedrock on every call. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([pscustomobject])] Param( # The default world seed to store, for use by cmdlets that accept a -Seed parameter. [Parameter()] [long] $Seed, # The default edition to store, for use by cmdlets that accept an -Edition parameter. [Parameter()] [ValidateSet('Java', 'Bedrock')] [string] $Edition ) if (-not $PSBoundParameters.ContainsKey('Seed') -and -not $PSBoundParameters.ContainsKey('Edition')) { throw 'At least one of -Seed or -Edition must be supplied.' } $Directory = Join-Path -Path $HOME -ChildPath '.minecraft' $Path = Join-Path -Path $Directory -ChildPath 'defaults.json' $Defaults = if (Test-Path -Path $Path) { Get-Content -Path $Path -Raw | ConvertFrom-Json } else { [pscustomobject]@{} } $Changes = [System.Collections.Generic.List[string]]::new() if ($PSBoundParameters.ContainsKey('Seed')) { $Defaults | Add-Member -MemberType NoteProperty -Name 'Seed' -Value $Seed -Force $Changes.Add("Seed to $Seed") } if ($PSBoundParameters.ContainsKey('Edition')) { $Defaults | Add-Member -MemberType NoteProperty -Name 'Edition' -Value $Edition -Force $Changes.Add("Edition to $Edition") } if ($PSCmdlet.ShouldProcess($Path, "Set default $($Changes -join ' and ')")) { if (-not (Test-Path -Path $Directory)) { New-Item -Path $Directory -ItemType Directory -Force | Out-Null } $Defaults | ConvertTo-Json | Set-Content -Path $Path $Defaults } } function Test-SlimeChunk { <# .SYNOPSIS Tests whether the chunk containing a block coordinate is a slime chunk. .DESCRIPTION The Test-SlimeChunk cmdlet converts a pair of block X/Z coordinates to the chunk that contains them (as ConvertTo-ChunkCoordinates does), then tests whether that chunk is a slime chunk, using either edition's own algorithm: - Java (the default): seeds a java.util.Random instance from the world seed and the chunk's coordinates, and treats the chunk as a slime chunk if the first number it draws between 0 and 9 is 0 (a 10% chance per chunk). If -Seed isn't supplied, the default Seed previously stored via Set-MinecraftDefault is used instead. - Bedrock: hashes only the chunk's coordinates (the world seed plays no part) and runs that through a Mersenne Twister PRNG, with the same 1-in-10 check. Because the world seed isn't used, slime chunks sit at the same coordinates in every Bedrock world, and -Seed (explicit or default) is ignored. If -Edition isn't supplied, the default Edition previously stored via Set-MinecraftDefault is used instead, falling back to Java if no default Edition has been stored either. Slimes can otherwise only naturally spawn in swamp biomes, so slime chunks are the usual way to farm them below layer 40 elsewhere. .EXAMPLE Test-SlimeChunk -Seed 12345 -X 48 -Z -32 Returns whether the chunk containing block X 48, Z -32 is a slime chunk for Java world seed 12345. .EXAMPLE Test-SlimeChunk -Edition Bedrock -X 48 -Z -32 Returns whether the chunk containing block X 48, Z -32 is a slime chunk in Bedrock Edition. No -Seed is needed, since Bedrock slime chunks don't depend on the world seed. .EXAMPLE [pscustomobject]@{ X = 48; Z = -32 } | Test-SlimeChunk -Seed 12345 X and Z can also be supplied via the pipeline by property name, e.g. from New-Coordinates, ConvertTo-NetherCoordinates or ConvertTo-OverworldCoordinates. .EXAMPLE Set-MinecraftDefault -Seed 12345 Test-SlimeChunk -X 48 -Z -32 Once a default Seed has been stored, -Seed can be omitted (Java only). .EXAMPLE Set-MinecraftDefault -Edition Bedrock Test-SlimeChunk -X 48 -Z -32 Once a default Edition has been stored, -Edition can be omitted too. #> [CmdletBinding()] [OutputType([bool])] Param( # The block X coordinate to test. [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] [double] $X, # The block Z coordinate to test. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [double] $Z, # The world seed. Only used for the Java edition; if omitted there, the default Seed stored via # Set-MinecraftDefault is used instead. [Parameter(Position = 2)] [long] $Seed, # Which edition's slime-chunk algorithm to use. If omitted, the default Edition stored via # Set-MinecraftDefault is used, falling back to Java if no default Edition has been stored either. [ValidateSet('Java', 'Bedrock')] [string] $Edition ) Begin { if ($PSBoundParameters.ContainsKey('Edition')) { $EffectiveEdition = $Edition } else { $DefaultEdition = Get-MinecraftDefault -Name 'Edition' $EffectiveEdition = if ($DefaultEdition) { $DefaultEdition } else { 'Java' } } if ($EffectiveEdition -eq 'Java') { if ($PSBoundParameters.ContainsKey('Seed')) { $EffectiveSeed = $Seed } else { $DefaultSeed = Get-MinecraftDefault -Name 'Seed' if ($null -eq $DefaultSeed) { throw 'No -Seed was supplied and no default Seed has been set. Use Set-MinecraftDefault -Seed <value> to set one.' } $EffectiveSeed = [long]$DefaultSeed } } } Process { $ChunkX = [int][Math]::Floor($X / 16) $ChunkZ = [int][Math]::Floor($Z / 16) $Mask32 = [System.Numerics.BigInteger]::Pow(2, 32) if ($EffectiveEdition -eq 'Java') { # Minecraft derives the per-chunk seed using Java's wrapping 32-bit integer arithmetic, so # each intermediate multiplication below is explicitly wrapped to 32 bits to match it -- using # the true (unwrapped) product at those steps would produce a different, incorrect seed. $Half32 = [System.Numerics.BigInteger]::Pow(2, 31) function ConvertTo-WrappedInt32 { param([System.Numerics.BigInteger]$Value) $Wrapped = (($Value % $Mask32) + $Mask32) % $Mask32 if ($Wrapped -ge $Half32) { $Wrapped - $Mask32 } else { $Wrapped } } $ChunkXSquared = ConvertTo-WrappedInt32 ([System.Numerics.BigInteger]$ChunkX * $ChunkX) $ChunkZSquared = ConvertTo-WrappedInt32 ([System.Numerics.BigInteger]$ChunkZ * $ChunkZ) $Term1 = ConvertTo-WrappedInt32 ($ChunkXSquared * 0x4c1906) $Term2 = ConvertTo-WrappedInt32 ([System.Numerics.BigInteger]$ChunkX * 0x5ac0db) $Term3 = $ChunkZSquared * [System.Numerics.BigInteger]0x4307a7 $Term4 = ConvertTo-WrappedInt32 ([System.Numerics.BigInteger]$ChunkZ * 0x5f24f) $ChunkSeed = ([System.Numerics.BigInteger]$EffectiveSeed + $Term1 + $Term2 + $Term3 + $Term4) -bxor 0x3ad8025f # Java's java.util.Random linear congruential generator. $Multiplier = [System.Numerics.BigInteger]0x5DEECE66D $Mask48 = ([System.Numerics.BigInteger]1 -shl 48) - 1 $State = ($ChunkSeed -bxor $Multiplier) -band $Mask48 $State = ($State * $Multiplier + 0xB) -band $Mask48 # Equivalent to Random.nextInt(10): take the top 31 bits of the state and reduce modulo 10. $Bits = [int]($State -shr 17) ($Bits % 10) -eq 0 } else { # Bedrock doesn't use the world seed at all -- it hashes only the chunk coordinates, so the # same chunk is a slime chunk (or isn't) in every Bedrock world. $ProductUnsigned = ((([System.Numerics.BigInteger]$ChunkX * 0x1f1f1f1f) % $Mask32) + $Mask32) % $Mask32 $ChunkZUnsigned = (([System.Numerics.BigInteger]$ChunkZ % $Mask32) + $Mask32) % $Mask32 $BedrockSeed = $ProductUnsigned -bxor $ChunkZUnsigned $Value = Get-Mt19937FirstUInt32 -Seed $BedrockSeed ($Value % 10) -eq 0 } } } function Get-Mt19937FirstUInt32 { <# .SYNOPSIS Returns the first 32-bit value produced by a Mersenne Twister (MT19937) PRNG for a given seed. .DESCRIPTION Implements the standard MT19937 algorithm just far enough to extract the very first tempered output, which is all that Test-SlimeChunk needs for Bedrock Edition's slime-chunk check. A full implementation would track an index and lazily re-twist every 624 values, but since only one value is ever drawn here, the state is seeded, twisted once, and the first word is tempered and returned directly. #> [CmdletBinding()] [OutputType([System.Numerics.BigInteger])] Param( # The 32-bit (0 to 2^32-1) seed to initialize the generator with. [Parameter(Mandatory)] [System.Numerics.BigInteger] $Seed ) # A hex literal with its top bit set (e.g. 0x80000000) parses as a *signed* Int32 in PowerShell, so # casting it straight to BigInteger carries that sign through as infinite-precision two's complement -- # corrupting every -band/-bxor against a value with bits set above position 31. The L suffix forces # Int64 parsing first, which (unlike the cast) correctly zero-extends the 32-bit pattern as positive. $Mask32 = [System.Numerics.BigInteger]::Pow(2, 32) $UpperMask = [System.Numerics.BigInteger]0x80000000L $LowerMask = [System.Numerics.BigInteger]0x7fffffffL $MatrixA = [System.Numerics.BigInteger]0x9908b0dfL $StateSize = 624 $Offset = 397 $State = [System.Numerics.BigInteger[]]::new($StateSize) $State[0] = $Seed % $Mask32 for ($i = 1; $i -lt $StateSize; $i++) { $Previous = $State[$i - 1] $State[$i] = ((1812433253 * ($Previous -bxor ($Previous -shr 30))) + $i) % $Mask32 } # The standard twist pass, applied once since only the first output word is ever needed. for ($i = 0; $i -lt $StateSize; $i++) { $Combined = ($State[$i] -band $UpperMask) -bor ($State[($i + 1) % $StateSize] -band $LowerMask) $Shifted = $Combined -shr 1 if (($Combined % 2) -ne 0) { $Shifted = $Shifted -bxor $MatrixA } $State[$i] = $State[($i + $Offset) % $StateSize] -bxor $Shifted } $Value = $State[0] $Value = $Value -bxor ($Value -shr 11) $Value = $Value -bxor (($Value -shl 7) -band 0x9D2C5680L) $Value = $Value -bxor (($Value -shl 15) -band 0xEFC60000L) $Value = $Value -bxor ($Value -shr 18) $Value % $Mask32 } |