Nethack Launcher
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

416 lines
10 KiB

package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"time"
"github.com/unixvoid/glogger"
"golang.org/x/crypto/sha3"
"gopkg.in/gcfg.v1"
"gopkg.in/redis.v5"
)
type Config struct {
NethackLauncher struct {
Loglevel string
ServerDisplay string
NethackVersion string
InProgressDir string
RecordLocation string
ReclistLocation string
BootstrapDelay time.Duration
}
Redis struct {
Host string
Password string
}
}
var (
config = Config{}
)
func main() {
// read conf file
readConf()
// init config file and logger
initLogger()
// init redis connection
redisClient, redisErr := initRedisConnection()
if redisErr != nil {
glogger.Debug.Printf("redis connection cannot be made, trying again in %s second(s)\n", config.NethackLauncher.BootstrapDelay*time.Second)
time.Sleep(config.NethackLauncher.BootstrapDelay * time.Second)
redisClient, redisErr = initRedisConnection()
if redisErr != nil {
glogger.Error.Println("redis connection cannot be made, exiting.")
panic(redisErr)
}
} else {
glogger.Debug.Println("connection to redis succeeded.")
}
screenFunction := printWelcomeScreen(redisClient)
fmt.Printf("screen %s recieved\n", screenFunction)
}
func readConf() {
// init config file
err := gcfg.ReadFileInto(&config, "config.gcfg")
if err != nil {
panic(fmt.Sprintf("Could not load config.gcfg, error: %s\n", err))
}
}
func initLogger() {
// init logger
if config.NethackLauncher.Loglevel == "debug" {
glogger.LogInit(os.Stdout, os.Stdout, os.Stdout, os.Stderr)
} else if config.NethackLauncher.Loglevel == "cluster" {
glogger.LogInit(os.Stdout, os.Stdout, ioutil.Discard, os.Stderr)
} else if config.NethackLauncher.Loglevel == "info" {
glogger.LogInit(os.Stdout, ioutil.Discard, ioutil.Discard, os.Stderr)
} else {
glogger.LogInit(ioutil.Discard, ioutil.Discard, ioutil.Discard, os.Stderr)
}
}
func initRedisConnection() (*redis.Client, error) {
// init redis connection
redisClient := redis.NewClient(&redis.Options{
Addr: config.Redis.Host,
Password: config.Redis.Password,
DB: 0,
})
_, redisErr := redisClient.Ping().Result()
return redisClient, redisErr
}
func checkFiles() {
// make sure record file exists
if _, err := os.Stat(config.NethackLauncher.RecordLocation); os.IsNotExist(err) {
glogger.Info.Printf("record file not found in %s\n", config.NethackLauncher.RecordLocation)
fmt.Printf("%s\n", err)
os.Exit(1)
}
// make sure reclist bin exists
if _, err := os.Stat(config.NethackLauncher.ReclistLocation); os.IsNotExist(err) {
glogger.Info.Printf("reclist binary not found in %s\n", config.NethackLauncher.ReclistLocation)
fmt.Printf("%s\n", err)
os.Exit(1)
}
}
func clearScreen() {
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
cmd.Run()
}
func printWelcomeScreen(redisClient *redis.Client) string {
clearScreen()
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay)
println("")
println(" Not logged in.")
println("")
println(" l) Login")
println(" r) Register new user")
println(" w) Watch games in progress")
println(" q) Quit")
println("")
fmt.Printf(">> ")
// disable input buffering
exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
// do not display entered characters on the screen
exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
var b []byte = make([]byte, 1)
for {
os.Stdin.Read(b)
switch string(b) {
case "l":
// restart display
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run()
clearScreen()
printLoginScreen(redisClient)
case "r":
// restart display
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run()
clearScreen()
printRegisterScreen(redisClient)
case "w":
clearScreen()
printProgressScreen()
return ("w")
case "q":
clearScreen()
os.Exit(0)
default:
}
}
}
func printUserScreen(redisClient *redis.Client, username string) string {
clearScreen()
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay)
println("")
fmt.Printf(" Logged in as: %s\n", username)
println("")
println(" l) Logout")
println(" w) Watch games in progress")
println(" e) Edit config")
fmt.Printf(" p) Play NetHack %s\n", config.NethackLauncher.NethackVersion)
println(" q) Quit")
println("")
fmt.Printf(">> ")
// disable input buffering
exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
// do not display entered characters on the screen
exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
var b []byte = make([]byte, 1)
for {
os.Stdin.Read(b)
switch string(b) {
case "l":
// restart display
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run()
clearScreen()
printWelcomeScreen(redisClient)
case "p":
// restart display
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run()
clearScreen()
5 years ago
nh := exec.Command("nethack", "-u", username)
nh.Stdout = os.Stdout
nh.Stdin = os.Stdin
nh.Stderr = os.Stderr
nh.Run()
printUserScreen(redisClient, username)
case "q":
clearScreen()
os.Exit(0)
default:
}
}
}
func printLoginScreen(redisClient *redis.Client) {
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay)
println("")
println(" Please enter your username. (blank entry aborts)")
println("")
fmt.Printf(">> ")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
if scanner.Text() == "" {
printWelcomeScreen(redisClient)
}
// check redis for user
username := scanner.Text()
storedHash, err := redisClient.Get(fmt.Sprintf("user:%s", username)).Result()
if err != nil {
// user does not exist
fmt.Printf(" There was a problem with your last entry.\n>> ")
} else {
// get password from user and compare
// turn off echo display
exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
noPass := true
for noPass {
fmt.Printf("\n Please enter your password (blank entry aborts).\n>> ")
reader := bufio.NewReader(os.Stdin)
typedAuth, _ := reader.ReadString('\n')
typedAuth = strings.Replace(typedAuth, "\n", "", -1)
if typedAuth == "" {
// exit to main menu
printWelcomeScreen(redisClient)
}
// get hash of typedAuth
typedHash := sha3.Sum512([]byte(typedAuth))
if fmt.Sprintf("%x", typedHash) == storedHash {
// user authed
printUserScreen(redisClient, username)
noPass = false
} else {
fmt.Printf("\n There was a problem with your last entry.")
}
}
}
}
if err := scanner.Err(); err != nil {
fmt.Printf("%s\n", err)
}
}
func printRegisterScreen(redisClient *redis.Client) {
// TODO : configure password restriction checking
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay)
println("")
println(" Welcome new user. Please enter a username")
println(" Only characters and numbers are allowed, with no spaces.")
println(" 20 characters max. (blank entry aborts)")
println("")
fmt.Printf(">> ")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
if scanner.Text() == "" {
printWelcomeScreen(redisClient)
}
// check redis for user
username := scanner.Text()
_, err := redisClient.Get(fmt.Sprintf("user:%s", username)).Result()
if err != redis.Nil {
// user already exists
fmt.Printf("There was a problem with your last entry.\n>> ")
} else {
// set up user
// turn off echo display
exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
reader := bufio.NewReader(os.Stdin)
noPass := true
sec := ""
for noPass {
// pull pass the first time
fmt.Printf(" Please enter your password (blank entry aborts).\n>> ")
sec0, _ := reader.ReadString('\n')
sec0 = strings.Replace(sec0, "\n", "", -1)
if sec0 == "" {
printWelcomeScreen(redisClient)
}
// pull pass the second time
fmt.Printf("\n Please enter your password again.\n>> ")
sec1, _ := reader.ReadString('\n')
sec1 = strings.Replace(sec1, "\n", "", -1)
// make sure passwords match
if sec0 == sec1 {
sec = sec0
noPass = false
} else {
fmt.Println("Lets try that again.")
}
}
// set user in redis
secHash := sha3.Sum512([]byte(sec))
redisClient.Set(fmt.Sprintf("user:%s", username), fmt.Sprintf("%x", secHash), 0).Err()
printUserScreen(redisClient, username)
}
}
if err := scanner.Err(); err != nil {
fmt.Printf("%s\n", err)
}
}
func printProgressScreen() {
// print header
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay)
println("")
// check directory for live players
isEmpty, err := checkDir(config.NethackLauncher.InProgressDir)
if err != nil {
panic(err)
}
if isEmpty {
println(" No live players currently (blank entry returns)")
} else {
println(" Choose a player to spectate (blank entry aborts)")
}
println("")
// populate inProg map
// create map for storing in progress data
inProg := make(map[int]string)
inProgTimer := 1
progFiles, _ := ioutil.ReadDir(config.NethackLauncher.InProgressDir)
for _, f := range progFiles {
// grab username and path into user[]
user := strings.Split(f.Name(), ":")
// get resolution lines from file
res0 := ""
res1 := ""
filePath := fmt.Sprintf("%s/%s", config.NethackLauncher.InProgressDir, f.Name())
fileIO, err := os.OpenFile(filePath, os.O_RDWR, 0600)
if err != nil {
panic(err)
}
defer fileIO.Close()
rawBytes, err := ioutil.ReadAll(fileIO)
if err != nil {
panic(err)
}
lines := strings.Split(string(rawBytes), "\n")
for i, line := range lines {
if i == 1 {
res1 = line
}
if i == 2 {
res0 = line
}
}
// print user line
fmt.Printf(" %d) %s\t%sx%s\t%s\n", inProgTimer, user[0], res0, res1, user[1])
// add user line to map
inProg[inProgTimer] = f.Name()
inProgTimer++
}
println("")
println(">>")
// print out map
os.Exit(0)
}
func gethighscore(w http.ResponseWriter, r *http.Request) {
// run script
output, err := exec.Command(config.NethackLauncher.ReclistLocation,
"-f",
config.NethackLauncher.RecordLocation).CombinedOutput()
if err != nil {
fmt.Printf("%s\n", err)
}
fmt.Fprintf(w, "%s\n", output)
}
func checkDir(dirName string) (bool, error) {
f, err := os.Open(dirName)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1)
if err == io.EOF {
return true, nil
}
return false, err
}