aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorFuwn <[email protected]>2021-07-04 02:56:14 -0700
committerFuwn <[email protected]>2021-07-04 02:56:14 -0700
commit38ddbd97122ca71777c500df101f1274b3f11900 (patch)
tree9ff64f8446646413a4832fd666d94454ad01f603 /pkg
downloadarchived-munch-38ddbd97122ca71777c500df101f1274b3f11900.tar.xz
archived-munch-38ddbd97122ca71777c500df101f1274b3f11900.zip
feat(munch): :star:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/config/config.go25
-rw-r--r--pkg/server/distributor.go58
-rw-r--r--pkg/server/hub.go70
-rw-r--r--pkg/server/server.go10
-rw-r--r--pkg/server/utils/decode.go24
-rw-r--r--pkg/server/utils/encode.go70
-rw-r--r--pkg/server/utils/hex.go31
7 files changed, 288 insertions, 0 deletions
diff --git a/pkg/config/config.go b/pkg/config/config.go
new file mode 100644
index 0000000..b3a3314
--- /dev/null
+++ b/pkg/config/config.go
@@ -0,0 +1,25 @@
+// Copyright (C) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+package config
+
+import (
+ "github.com/spf13/viper"
+ "log"
+)
+
+func Setup() {
+ viper.SetConfigName("config.yml")
+ viper.SetConfigType("yaml")
+ viper.AddConfigPath(".")
+
+ if err := viper.ReadInConfig(); err != nil {
+ if _, ok := err.(viper.ConfigFileNotFoundError); ok {
+ log.Panicln("Cannot read configuration file:", err)
+ } else {
+ log.Panicln("Read configuration file but an error occurred anyway:", err)
+ }
+ }
+
+ viper.WatchConfig()
+}
diff --git a/pkg/server/distributor.go b/pkg/server/distributor.go
new file mode 100644
index 0000000..d9c6bcf
--- /dev/null
+++ b/pkg/server/distributor.go
@@ -0,0 +1,58 @@
+// Copyright (C) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+package server
+
+import (
+ "encoding/binary"
+ "github.com/Whirlsplash/munch/pkg/server/utils"
+ "github.com/spf13/viper"
+ "log"
+ "net"
+ "strconv"
+)
+
+func doDistributor() string {
+ tcpAddr, _ := net.ResolveTCPAddr(
+ "tcp",
+ viper.GetString("worlds.server.ip")+":"+viper.GetString("worlds.server.port"),
+ )
+ conn, _ := net.DialTCP("tcp", nil, tcpAddr)
+
+ // PROPREQ
+ conn.Write([]byte("\x03\xff\x0a"))
+ reply := make([]byte, 1024)
+ conn.Read(reply)
+ log.Println("AutoServer: PROPREQ")
+
+ // SESSINIT
+ conn.Write(utils.EncodeSessionInitialization(
+ viper.GetString("worlds.account.username"),
+ viper.GetString("worlds.account.password"),
+ utils.AutoServer,
+ ))
+ reply = make([]byte, 1024)
+ conn.Read(reply)
+ log.Println("AutoServer: SESSINIT")
+
+ // PROPUPD
+ conn.Write(utils.EncodePropertyUpdate(viper.GetString("worlds.account.avatar")))
+ reply = make([]byte, 1024)
+ conn.Read(reply)
+ log.Println("AutoServer: PROPUPD")
+
+ // ROOMIDRQ
+ conn.Write([]byte(
+ "\x26\x01\x14\x22\x47\x72\x6f\x75\x6e\x64\x5a\x65\x72\x6f\x23\x73" +
+ "\x74\x61\x69\x72\x63\x61\x73\x65\x31\x3c\x64\x69\x6d\x65\x6e\x73" +
+ "\x69\x6f\x6e\x2d\x31\x3e",
+ ))
+ reply = make([]byte, 1024)
+ conn.Read(reply)
+ port := strconv.Itoa(int(binary.BigEndian.Uint16(reply[44:46])))
+ log.Println("AutoServer: ROOMIDRQ")
+
+ conn.Close()
+
+ return port
+}
diff --git a/pkg/server/hub.go b/pkg/server/hub.go
new file mode 100644
index 0000000..9e34523
--- /dev/null
+++ b/pkg/server/hub.go
@@ -0,0 +1,70 @@
+// Copyright (C) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+package server
+
+import (
+ "github.com/Whirlsplash/munch/pkg/server/utils"
+ "github.com/spf13/viper"
+ "log"
+ "net"
+)
+
+func doHub(port string) {
+ tcpAddr, _ := net.ResolveTCPAddr(
+ "tcp",
+ (viper.GetString("worlds.server.ip") + ":" + port),
+ )
+ conn, _ := net.DialTCP("tcp", nil, tcpAddr)
+
+ // PROPREQ
+ conn.Write([]byte("\x03\xff\x0a"))
+ reply := make([]byte, 1024)
+ conn.Read(reply)
+ log.Println("RoomServer: PROPREQ")
+
+ // SESSINIT
+ conn.Write(utils.EncodeSessionInitialization(
+ viper.GetString("worlds.account.username"),
+ viper.GetString("worlds.account.password"),
+ utils.RoomServer,
+ ))
+ reply = make([]byte, 1024)
+ conn.Read(reply)
+ log.Println("RoomServer: SESSINIT")
+
+ // SUB-DIST
+ conn.Write([]byte(
+ "\x0d\x01\x16\x00\x02\x00\x00\x00\xfa\x00\x00\x0a\xf8" +
+ "\x0d\x01\x16\x00\x01\x00\x00\x02\xee\x00\x00\x0c\xf9" +
+ "\x0d\x01\x16\x00\x07\x00\x00\x36\xaf\x00\x00\x09\xbd" +
+ "\x0d\x01\x16\x00\x06\xf1\x77\x00\x00\x01\x90\x0d\xcf\x0d\x01\x16" +
+ "\x98\xee\x00\x00\x00\xc8\x00\x28\x0e\x67\x0d\x01\x16\x00\x04\x00" +
+ "\x00\x00\xfa\x00\x00\x0c\xb7\x0d\x01\x16\x00\x03\x00\x00\x02\xee" +
+ "\x00\x00\x0c\x30\x0d\x01\x16\x00\x09\x01\xf4\x03\xe8\x01\xf4\x09" +
+ "\xd5\x0d\x01\x16\x00\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x0f\x01" +
+ "\x12\x00\x08\x00\x01\x04\xcc\x09\xa1\x00\x00\x01\x25\x07\x01\x18" +
+ "\x00\x01\x0c\xf9\x07\x01\x18\x00\x03\x0c\x30\x07\x01\x18\x00\x09" +
+ "\x09\xd5\x07\x01\x18\x00\x07\x09\xbd\x07\x01\x18\x00\x06\x0d\xcf" +
+ "\x07\x01\x18\x98\xee\x0e\x67\x07\x01\x18\x00\x04\x0c\xb7\x07\x01" +
+ "\x18\x00\x02\x0a\xf8",
+ ))
+ reply = make([]byte, 1024)
+ conn.Read(reply)
+ log.Println("RoomServer: SUBSCRIB")
+
+ // Listen for whispers
+ for {
+ reply = make([]byte, 1024)
+ conn.Read(reply)
+ if reply[2] == 17 {
+ decodedText := utils.DecodeText(reply)
+ log.Printf(
+ "RoomServer: WHISPER: %s: %s\n",
+ decodedText.Author, decodedText.Message,
+ )
+ }
+ }
+
+ conn.Close()
+}
diff --git a/pkg/server/server.go b/pkg/server/server.go
new file mode 100644
index 0000000..d85a5ed
--- /dev/null
+++ b/pkg/server/server.go
@@ -0,0 +1,10 @@
+// Copyright (C) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+package server
+
+func Do() {
+ // The Distributor isn't actually needed for Munch to work, the only reason
+ // that we use it is because it gives us the correct Hub port to connect to.
+ doHub(doDistributor())
+}
diff --git a/pkg/server/utils/decode.go b/pkg/server/utils/decode.go
new file mode 100644
index 0000000..e819b31
--- /dev/null
+++ b/pkg/server/utils/decode.go
@@ -0,0 +1,24 @@
+// Copyright (C) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+package utils
+
+import "strconv"
+
+type Text struct {
+ Author string
+ Message string
+}
+
+// Decode a TEXT command from hexadecimal to a Text struct
+func DecodeText(data []byte) Text {
+ authorLength, _ := strconv.ParseInt(strconv.Itoa(int(data[4])), 16, 32)
+ authorLast := authorLength + 5
+ messageLength, _ := strconv.ParseInt(strconv.Itoa(int(data[authorLast])), 16, 32)
+ messageStart := authorLast + 1
+
+ return Text{
+ Author: string(data[5:(authorLast)]),
+ Message: string(data[messageStart : messageStart+(messageLength)]),
+ }
+}
diff --git a/pkg/server/utils/encode.go b/pkg/server/utils/encode.go
new file mode 100644
index 0000000..86e5500
--- /dev/null
+++ b/pkg/server/utils/encode.go
@@ -0,0 +1,70 @@
+// Copyright (C) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+package utils
+
+const (
+ AutoServer = iota
+ RoomServer
+)
+
+func EncodeSessionInitialization(username string, password string, serverType int) []byte {
+ data := ""
+
+ // Data length
+ if serverType == AutoServer {
+ data += "\x2b"
+ } else {
+ data += "\x2c"
+ }
+
+ // Command header and other stuff we don't need to worry about
+ data += "\x01\x06\x03\x02\x32\x34\x09\x0a\x32\x30\x32\x30\x30\x33\x31" +
+ "\x32\x30\x30"
+
+ if serverType == RoomServer {
+ data += "\x07\x02\x32\x34"
+ }
+
+ data +=
+ // Username
+ "\x02" + string(rune(len(username))) + ISO88591ToString(username) +
+
+ // Password
+ "\x06" + string(rune(len(password))) + ISO88591ToString(password)
+
+ if serverType == AutoServer {
+ data += "\x0c\x01\x31"
+ }
+
+ return []byte(data)
+}
+
+func EncodeBuddyListUpdate(buddy string) []byte {
+ // Command header
+ data := "\x01\x1d"
+
+ // Buddy UTF-8 length and UTF-8
+ data += string(rune(len(buddy))) + ISO88591ToString(buddy)
+
+ // Buddy "add"
+ data += "\x01"
+
+ // Data length
+ data = (string(rune(len(data) + 1))) + data
+
+ return []byte(data)
+}
+
+func EncodePropertyUpdate(avatar string) []byte {
+ // Command header and extra stuff we don't need to worry about
+ data := "\x01\x0f\x00\x05\x40\x01"
+
+ // Avatar UTF-8 length and UTF-8
+ data += string(rune(len("avatar:"+avatar))) + ISO88591ToString("avatar:"+avatar)
+
+ // Data length
+ data = (string(rune(len(data) + 1))) + data
+
+ return []byte(data)
+}
diff --git a/pkg/server/utils/hex.go b/pkg/server/utils/hex.go
new file mode 100644
index 0000000..a5393aa
--- /dev/null
+++ b/pkg/server/utils/hex.go
@@ -0,0 +1,31 @@
+package utils
+
+import "unicode/utf8"
+
+// https://stackoverflow.com/a/43461796/14452787
+//
+// I cannot explain the joy that flew through me once I found this ISO-8859-1
+// (Latin-1) to UTF-8 converter. I was looking for a valid solution on how to
+// insert the username and password into a hex-escaped string sequence for
+// EncodeSessionInitialization for hours and this finally solved all of my
+// problems.
+func ISO88591ToString(iso string) string {
+ var utf []rune
+ for i := 0; i < len(iso); i++ {
+ r := iso[i]
+ if utf == nil {
+ if r < utf8.RuneSelf {
+ continue
+ }
+ utf = make([]rune, len(iso))
+ for j, r := range iso[:i] {
+ utf[j] = rune(r)
+ }
+ }
+ utf[i] = rune(r)
+ }
+ if utf == nil {
+ return string(iso)
+ }
+ return string(utf)
+}