Created
March 6, 2024 22:13
-
-
Save roflsandwich/9acf80013ffc31a66e4a4b509a07a152 to your computer and use it in GitHub Desktop.
RCON Client in Powershell, works in games such as Palworld and Minecraft
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Variables | |
$serverAddress = "127.0.0.1" | |
$serverPort = 25575 | |
$Password = "" | |
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop | |
class RconPacket { | |
# https://developer.valvesoftware.com/wiki/Source_RCON_Protocol | |
hidden [byte[]] $pktSize | |
[byte[]] $PktId | |
[byte[]] $PktCmdType | |
[byte[]] $PktCmdPayload | |
RconPacket( | |
[int] $Type, | |
[string] $Command | |
){ | |
$enc = [System.Text.Encoding]::ASCII | |
# Create empty bytes[] | |
$this.pktSize = [byte[]]::new(4) | |
$this.pktId = [byte[]]::new(4) | |
$this.PktCmdType = [byte[]]::new(4) | |
$this.PktCmdPayload = [byte[]]::new(1) | |
# Validate type | |
if (($Type -gt 4) -or ($Type -lt 0)) { | |
throw "Invalid CMD Type: $Type" | |
} | |
# Construct Packet | |
$this.pktId = $enc.GetBytes(([guid]::NewGuid()).guid.split("-")[1]) | |
$this.PktCmdType[0] = $type | |
$this.PktCmdPayload = $enc.GetBytes($Command) + 0x00 | |
# Includes \0 terminator | |
$this.pktSize = [BitConverter]::GetBytes($this.PktCmdPayload.Length + 9) | |
# [BitConverter]::GetBytes() already returns a Byte[] of size 4 | |
} | |
[PSCustomObject] Construct() { | |
$returnObj = $this.pktSize + $this.pktId + $this.PktCmdType + $this.PktCmdPayload + 0x00 | |
return $returnObj | |
} | |
} | |
class RconClient { | |
hidden [System.Net.Sockets.Socket] $_socket | |
hidden [bool] $_isAuthenticated | |
[string] $ServerAddress | |
[int] $ServerPort | |
RconClient( | |
[String]$ServerAddress, | |
[int]$ServerPort | |
){ | |
# Validate params | |
if ($ServerPort -gt 65535 -or $ServerPort -le 0) { | |
throw "Server port '$ServerPort' is outside of acceptable range." | |
} | |
# Init class vars | |
$this._isAuthenticated = $false | |
$this.ServerAddress = $ServerAddress | |
$this.ServerPort = $ServerPort | |
try { | |
$this._socket = [System.Net.Sockets.Socket]::New( | |
[System.Net.Sockets.AddressFamily]::InterNetwork, | |
[System.Net.Sockets.SocketType]::Stream, | |
[System.Net.Sockets.ProtocolType]::TCP | |
) | |
$this._socket.Connect($serverAddress, $serverPort) | |
} catch { | |
Write-Host "Server connection has failed." -f red | |
throw $_.Exception.Message | |
} | |
} | |
Quit() { | |
Write-Warning "Destroying socket." | |
if ($this._socket.Connected) { | |
$this._socket.Close() | |
} else { | |
throw "Already in disconnected state." | |
} | |
} | |
Authenticate( | |
[string] $Password | |
){ | |
if ($this._isAuthenticated) { | |
throw "Already authenticated." | |
} | |
$p = New-Object RconPacket 3, $Password | |
$response = $this._sendSocket($p) | |
# Parse buffer | |
# Server will return FF FF FF FF (-1) in the ID field if auth failed or not authenticated | |
# The following will return FALSE if ID field is not FF FF FF FF, indicating SUCCESS | |
$responseVerdict = ((Compare-Object $response[4..7] @(,0xFF * 4)).Count -gt 0) | |
if ($responseVerdict) { | |
$this._isAuthenticated = $true | |
} else { | |
$this._isAuthenticated = $false | |
throw "Authentication failed due to bad password." | |
} | |
} | |
[string] Send( | |
[string] $Command | |
){ | |
if (!$this._socket.Connected) { | |
throw "Socket is not connected." | |
} | |
if (!$this._isAuthenticated) { | |
throw "Client not yet authenticated." | |
} | |
# Response begins at the 13th byte | |
$t = $this._sendSocket((New-Object RconPacket 2, $Command)) | |
$response = [System.Text.Encoding]::ASCII.GetString($t[12..($t.length)]) | |
return $response | |
} | |
Reconnect() { | |
if ($this._socket.Connected) { | |
throw "Socket is already connected." | |
} | |
$this._isAuthenticated = $false | |
try { | |
$this._socket.Connect($this.ServerAddress, $this.ServerPort) | |
} catch { | |
throw $_ | |
} | |
} | |
[byte[]] _sendSocket( | |
[RconPacket] $packet | |
){ | |
if (!$this._socket.Connected) { | |
throw "Socket is not connected." | |
} | |
try { | |
$toSend = $packet.Construct() | |
$buf = New-Object System.Byte[] 4096 | |
$this._socket.Send($toSend) | Out-Null | |
$this._socket.Receive($buf) | |
return $buf | |
} catch { | |
throw $_ | |
} | |
} | |
[bool] IsAuthenticated() { | |
return $this._isAuthenticated | |
} | |
[bool] IsConnected() { | |
return $this._socket.Connected | |
} | |
} | |
#Main Loop | |
# Connect and authenticate | |
try { | |
$RconClient = New-Object RconClient $serverAddress, $serverPort | |
$RconClient.Authenticate($Password) | |
} catch { | |
if ($RconClient.IsConnected()) { $RconClient.Quit() } | |
throw $_ | |
} | |
# Send commands | |
''; Write-Host 'You are now connected. Abort with "quit".' -f yellow | |
while ($true) { | |
Write-Host "> " -NoNewLine | |
$command = $Host.UI.ReadLine() | |
try { | |
if ($command -eq "quit") { | |
$RconClient.Quit() | |
exit | |
} | |
$response = $RconClient.Send($Command) | |
# Specific to minecraft | |
$response.split("/") | % { Write-Host "/$_" } | |
} catch { | |
throw $_ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment