// Command fakeark pretends to be an ARK server's RCON and query ports, so Megalodon can be tried // (and its dashboard looked at) without a real server. Players come and go, and chat appears. package main import ( "encoding/binary" "flag" "fmt" "io" "log" "math/rand" "net" "strings" "sync" "time" ) type server struct { mu sync.Mutex name string password string online map[string]string // steam id -> name chat []string pool [][2]string } func main() { rconAddr := flag.String("rcon", "127.0.0.1:27020", "RCON listen address") query := flag.String("query", "127.0.0.1:27015", "query (UDP) listen address, empty to disable") password := flag.String("password", "secret", "RCON password") name := flag.String("name", "Fake Island", "server name") flag.Parse() s := &server{name: *name, password: *password, online: map[string]string{}} for i, n := range []string{"Alex", "Bo", "Casey", "Dana", "Eli", "Fern", "Gus", "Hana"} { s.pool = append(s.pool, [2]string{fmt.Sprintf("7656119800000%04d", i+1), n}) } go s.simulate() if *query != "" { go s.serveQuery(*query) } ln, err := net.Listen("tcp", *rconAddr) if err != nil { log.Fatal(err) } log.Printf("fake ARK server %q: RCON %s (password %q), query %s", *name, *rconAddr, *password, *query) for { c, err := ln.Accept() if err != nil { log.Fatal(err) } go s.handle(c) } } func (s *server) simulate() { lines := []string{"hello everyone", "anyone want to breed rexes?", "my base got raided", "gg", "brb food"} for range time.Tick(6 * time.Second) { s.mu.Lock() p := s.pool[rand.Intn(len(s.pool))] if _, on := s.online[p[0]]; on { if rand.Intn(2) == 0 { delete(s.online, p[0]) } else { s.chat = append(s.chat, fmt.Sprintf("%s (%s the Tamer): %s", p[1], p[1], lines[rand.Intn(len(lines))])) } } else { s.online[p[0]] = p[1] } s.mu.Unlock() } } func (s *server) run(cmd string) string { s.mu.Lock() defer s.mu.Unlock() verb, rest, _ := strings.Cut(cmd, " ") switch strings.ToLower(verb) { case "listplayers": if len(s.online) == 0 { return "No Players Connected \n" } var sb strings.Builder n := 0 for id, name := range s.online { fmt.Fprintf(&sb, "%d. %s, %s\n", n, name, id) n++ } return sb.String() case "getchat": if len(s.chat) == 0 { return "Server received, But no response!!\n" } out := strings.Join(s.chat, "\n") + "\n" s.chat = nil return out case "saveworld": return "World Saved\n" case "broadcast", "serverchat": if strings.ToLower(verb) == "serverchat" { s.chat = append(s.chat, "SERVER: "+rest) } return "Server received, But no response!!\n" case "kickplayer", "banplayer": delete(s.online, strings.TrimSpace(rest)) return fmt.Sprintf("%s %s\n", verb, rest) } return "Server received, But no response!!\n" } func (s *server) handle(c net.Conn) { defer c.Close() id, typ, body, err := read(c) if err != nil || typ != 3 { return } write(c, id, 0, "") if body != s.password { write(c, -1, 2, "") return } write(c, id, 2, "") for { id, _, cmd, err := read(c) if err != nil { return } write(c, id, 0, s.run(cmd)) } } func write(w io.Writer, id, typ int32, body string) { buf := make([]byte, 12+len(body)+2) binary.LittleEndian.PutUint32(buf, uint32(10+len(body))) binary.LittleEndian.PutUint32(buf[4:], uint32(id)) binary.LittleEndian.PutUint32(buf[8:], uint32(typ)) copy(buf[12:], body) w.Write(buf) } func read(r io.Reader) (id, typ int32, body string, err error) { var h [4]byte if _, err = io.ReadFull(r, h[:]); err != nil { return } n := binary.LittleEndian.Uint32(h[:]) if n < 10 || n > 1<<16 { return 0, 0, "", io.ErrUnexpectedEOF } buf := make([]byte, n) if _, err = io.ReadFull(r, buf); err != nil { return } return int32(binary.LittleEndian.Uint32(buf)), int32(binary.LittleEndian.Uint32(buf[4:])), string(buf[8 : n-2]), nil } // serveQuery answers A2S_INFO with a challenge first, like a real server. func (s *server) serveQuery(addr string) { pc, err := net.ListenPacket("udp", addr) if err != nil { log.Print("query: ", err) return } buf := make([]byte, 100) for { n, from, err := pc.ReadFrom(buf) if err != nil { return } if n <= 25 { pc.WriteTo([]byte{0xff, 0xff, 0xff, 0xff, 0x41, 1, 2, 3, 4}, from) continue } s.mu.Lock() players := len(s.online) s.mu.Unlock() reply := []byte{0xff, 0xff, 0xff, 0xff, 0x49, 17} for _, v := range []string{s.name + " - (v350.2)", "TheIsland", "ark_survival_evolved", "ARK: Survival Evolved"} { reply = append(append(reply, v...), 0) } reply = append(reply, 0, 0, byte(players), 70, 0, 'd', 'w', 0, 1, '1', '.', '0', 0) pc.WriteTo(reply, from) } }