Skip to content

Commit

Permalink
Rewrite and update the code for the modern world.
Browse files Browse the repository at this point in the history
  • Loading branch information
icedream committed May 5, 2024
1 parent 3a42381 commit a20d0e6
Show file tree
Hide file tree
Showing 23 changed files with 653 additions and 465 deletions.
5 changes: 5 additions & 0 deletions cmd/icecon/imports_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package main

import (
_ "github.com/icedream/icecon/internal/ui/windows"
)
107 changes: 107 additions & 0 deletions cmd/icecon/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package main

import (
"fmt"
"log"
"strings"

"github.com/alecthomas/kingpin/v2"
"github.com/icedream/icecon/internal/rcon"
"github.com/icedream/icecon/internal/ui"

_ "github.com/icedream/icecon/internal/ui/console"
)

var (
flagCommand = kingpin.Flag("command",
"Run a one-off command and then exit.").
Short('c').String()
argAddress = kingpin.Arg("address",
"Server IP/hostname and port, written as \"server:port\".")
argPassword = kingpin.Arg("password", "The RCON password.")

password string
)

func usage() {
kingpin.Usage()
}

var (
hasGraphicalUI = ui.HasGraphicalUI()
flagGui *bool
)

func init() {
// only provide -gui/-g flag if there is a graphical user interface available
if hasGraphicalUI {
flagGui = kingpin.
Flag("gui", "Run as GUI (runs automatically as GUI if no arguments given, ignored if command flag used)").
Short('g').Bool()
}
}

func main() {
fmt.Println("IceCon - Icedream's RCON Client")
fmt.Println("\t\u00A9 2016-2024 Carl Kittelberger/Icedream")
fmt.Println()

argAddressTCP := argAddress.TCP()
argPasswordStr := argPassword.String()

kingpin.Parse()

// If no arguments, fall back to running the shell
wantGui := (*argAddressTCP == nil && *flagCommand == "") || *flagGui

// Command-line shell doesn't support starting up without arguments
// but graphical Windows UI does
if !(hasGraphicalUI && wantGui) {
argAddress = argAddress.Required()
argPassword = argPassword.Required()
kingpin.Parse()
}

// Initialize socket
rconClient := rcon.NewRconClient()
rconClient.InitSocket()
defer rconClient.Release()

// Set target address if given
if *argAddressTCP != nil {
rconClient.SetSocketAddr((*argAddressTCP).String())
}

// Get password
password = *argPasswordStr

// Run one-off command?
if *flagCommand != "" {
// Send
err := rconClient.Send(*flagCommand)
if err != nil {
log.Fatal(err)
return
}

// Receive
msg, err := rconClient.Receive()
if err != nil {
log.Fatal(err)
return
}
switch strings.ToLower(msg.Name) {
case "print":
fmt.Println(string(msg.Data))
}
return
}

// Which UI should be run?
log.Println("Starting user interface.")
if err := ui.Run(rconClient, wantGui); err != nil {
log.Fatal("User interface failed:", err)
return
}
log.Println("Done.")
}
4 changes: 4 additions & 0 deletions cmd/icecon/rsrc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package main

//go:generate go run -mod=mod github.com/josephspurrier/goversioninfo/cmd/goversioninfo -manifest "../../rsrc/app.manifest" -icon "../../rsrc/app.ico" -o "rsrc_windows_386.syso"
//go:generate go run -mod=mod github.com/josephspurrier/goversioninfo/cmd/goversioninfo -manifest "../../rsrc/app.manifest" -icon "../../rsrc/app.ico" -o "rsrc_windows_amd64.syso" -64
File renamed without changes.
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
module github.com/icedream/icecon

go 1.12
go 1.22

require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/icedream/go-q3net v0.1.0
github.com/icedream/ui2walk v0.0.0-20160513005918-6f3bcb07a270
github.com/josephspurrier/goversioninfo v1.4.0
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794
)

require (
github.com/akavel/rsrc v0.10.2 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
golang.org/x/sys v0.12.0 // indirect
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
)
12 changes: 7 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
Expand All @@ -7,19 +9,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/icedream/go-q3net v0.1.0 h1:ly5QS55sXAs7HunlCPDsUmS6QLYqP6kGBdupwufaiC4=
github.com/icedream/go-q3net v0.1.0/go.mod h1:2Y0epYeaR6uWXDMvapfsUkLDqAXhI8mp/J5LxO86eUU=
github.com/icedream/ui2walk v0.0.0-20160513005918-6f3bcb07a270 h1:tjLVsfoFJxX30ny02EEOjg3VXdoZA0uH8x3gw9YUM4U=
github.com/icedream/ui2walk v0.0.0-20160513005918-6f3bcb07a270/go.mod h1:6wS3BNtTpx4//e4hNWPUegvMQ9qT7iZ9RyvB8HmCtzQ=
github.com/josephspurrier/goversioninfo v1.4.0 h1:Puhl12NSHUSALHSuzYwPYQkqa2E1+7SrtAPJorKK0C8=
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
Expand All @@ -29,7 +32,6 @@ golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
110 changes: 110 additions & 0 deletions internal/rcon/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package rcon

import (
"bytes"
"errors"
"fmt"
"net"
"strings"

quake "github.com/icedream/go-q3net"
)

type Client struct {
address *net.UDPAddr
addressStr string
password string

socket *net.UDPConn
socketBuffer []byte
}

func NewRconClient() *Client {
socketBuffer := make([]byte, 64*1024)
return &Client{
socketBuffer: socketBuffer,
}
}

func (c *Client) SetSocketAddr(addr string) (err error) {
newAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return
}

c.address, c.addressStr = newAddr, addr

return
}

func (c *Client) SetPassword(pw string) {
c.password = pw
}

func (c *Client) Address() *net.UDPAddr {
return c.address
}

func (c *Client) AddressString() string {
return c.addressStr
}

func (c *Client) Password() string {
return c.password
}

func (c *Client) InitSocket() (err error) {
c.socket, err = net.ListenUDP("udp", nil)
if err != nil {
return
}

return
}

func (c *Client) udpReceiveAndUnmarshal() (msg *quake.Message, err error) {
length, _, err := c.socket.ReadFromUDP(c.socketBuffer)
if err != nil {
return
}

msg, err = quake.UnmarshalMessage(c.socketBuffer[0:length])
if err != nil {
return
}
return
}

func (c *Client) Receive() (msg *quake.Message, err error) {
msg, err = c.udpReceiveAndUnmarshal()
if err != nil {
return
}
if !strings.EqualFold(msg.Name, "print") {
err = errors.New("rcon: Unexpected response from server: " + msg.Name)
}
return
}

func (c *Client) Send(input string) (err error) {
buf := new(bytes.Buffer)
msg := &quake.Message{
Header: quake.OOBHeader,
Name: "rcon",
Data: []byte(fmt.Sprintf("%s %s", c.password, input)),
}
if err = msg.Marshal(buf); err != nil {
return
}
if _, err = c.socket.WriteToUDP(buf.Bytes(), c.address); err != nil {
return
}
return
}

func (c *Client) Release() {
if c.socket != nil {
c.socket.Close()
c.socket = nil
}
}
74 changes: 74 additions & 0 deletions internal/ui/console/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ui

import (
"bufio"
"log"
"os"
"strings"

"github.com/icedream/icecon/internal/rcon"
"github.com/icedream/icecon/internal/ui"
)

func init() {
ui.RegisterUserInterface(ui.UserInterfaceProvider{
New: NewConsoleUserInterface,
})
}

type consoleUserInterface struct {
rcon *rcon.Client
bufferedStdin *bufio.Reader
}

func NewConsoleUserInterface(rconClient *rcon.Client) (ui.UserInterface, error) {
return &consoleUserInterface{
rcon: rconClient,
bufferedStdin: bufio.NewReader(os.Stdin),
}, nil
}

func (ui *consoleUserInterface) readLineFromInput() (input string, err error) {
for {
if line, hasMoreInLine, err := ui.bufferedStdin.ReadLine(); err != nil {
return input, err
} else {
input += string(line)
if !hasMoreInLine {
break
}
}
}
return
}

func (ui *consoleUserInterface) Run() error {
for {
input, err := ui.readLineFromInput()
if err != nil {
log.Fatal(err)
continue
}

// "quit" => exit shell
if strings.EqualFold(strings.TrimSpace(input), "quit") {
break
}

err = ui.rcon.Send(input)
if err != nil {
log.Println(err)
continue
}
msg, err := ui.rcon.Receive()
if err != nil {
log.Println(err)
continue
}
switch strings.ToLower(msg.Name) {
case "print":
log.Println(string(msg.Data))
}
}
return nil
}
Loading

0 comments on commit a20d0e6

Please sign in to comment.