Matthew Faltys
5 years ago
17 changed files with 950 additions and 811 deletions
@ -1,809 +0,0 @@
|
||||
package main |
||||
|
||||
// TODO split up into multiple files
|
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
|
||||
"golang.org/x/crypto/sha3" |
||||
"gopkg.in/gcfg.v1" |
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
type Config struct { |
||||
NethackLauncher struct { |
||||
Loglevel string |
||||
ServerDisplay string |
||||
NethackVersion string |
||||
HackDir string |
||||
NhdatLocation string |
||||
RecoverBinary string |
||||
BootstrapDelay time.Duration |
||||
} |
||||
|
||||
Redis struct { |
||||
Host string |
||||
Password string |
||||
} |
||||
} |
||||
|
||||
var ( |
||||
config = Config{} |
||||
wg sync.WaitGroup |
||||
) |
||||
|
||||
func main() { |
||||
// read conf file
|
||||
readConf() |
||||
|
||||
// init redis connection
|
||||
redisClient, redisErr := initRedisConnection() |
||||
if redisErr != nil { |
||||
time.Sleep(config.NethackLauncher.BootstrapDelay * time.Second) |
||||
redisClient, redisErr = initRedisConnection() |
||||
if redisErr != nil { |
||||
panic(redisErr) |
||||
} |
||||
} else { |
||||
} |
||||
|
||||
// create initial files needed by nethack
|
||||
createInitialFiles() |
||||
|
||||
// start janitor
|
||||
go janitor(redisClient) |
||||
|
||||
// start homescreen
|
||||
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 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(fmt.Sprintf("%s/record", config.NethackLauncher.HackDir)); os.IsNotExist(err) { |
||||
fmt.Printf("record file not found in %s/record\n", config.NethackLauncher.HackDir) |
||||
fmt.Printf("%s\n", err) |
||||
os.Exit(1) |
||||
} |
||||
// make sure initial rcfile exists
|
||||
hackRCLoc := fmt.Sprintf("%s/.nethackrc", config.NethackLauncher.HackDir) |
||||
if _, err := os.Stat(hackRCLoc); os.IsNotExist(err) { |
||||
fmt.Printf("initial config file not found at: %s\n", hackRCLoc) |
||||
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(" h) View highscores") |
||||
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(redisClient, "") |
||||
case "h": |
||||
clearScreen() |
||||
printHighScores(redisClient, "") |
||||
case "q": |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
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(" c) Change password") |
||||
println(" w) Watch games in progress") |
||||
println(" h) View highscores") |
||||
println(" e) Edit config") |
||||
println(" r) Recover from crash") |
||||
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 "e": |
||||
hackRCLoc := fmt.Sprintf("%s/user/%s/.nethackrc", config.NethackLauncher.HackDir, username) |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
nh := exec.Command("vim", "-Z", hackRCLoc) |
||||
nh.Stdout = os.Stdout |
||||
nh.Stdin = os.Stdin |
||||
nh.Stderr = os.Stderr |
||||
nh.Run() |
||||
clearScreen() |
||||
printUserScreen(redisClient, username) |
||||
case "c": |
||||
printChangePasswordScreen(redisClient, username) |
||||
clearScreen() |
||||
case "w": |
||||
clearScreen() |
||||
printProgressScreen(redisClient, username) |
||||
case "h": |
||||
clearScreen() |
||||
printHighScores(redisClient, username) |
||||
case "p": |
||||
wg.Add(1) |
||||
currentTime := time.Now().UTC() |
||||
fulltime := currentTime.Format("2006-01-02.03:04:05") |
||||
go runGame(username, fulltime) |
||||
watcher := startWatcher(username, fulltime, redisClient) |
||||
wg.Wait() |
||||
close(watcher) |
||||
printUserScreen(redisClient, username) |
||||
case "r": |
||||
clearScreen() |
||||
recoverSave(redisClient, username) |
||||
case "q": |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
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.") |
||||
} |
||||
} |
||||
|
||||
// reset display
|
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
|
||||
// set user in redis
|
||||
secHash := sha3.Sum512([]byte(sec)) |
||||
redisClient.Set(fmt.Sprintf("user:%s", username), fmt.Sprintf("%x", secHash), 0).Err() |
||||
|
||||
// create user directories
|
||||
userPath := fmt.Sprintf("%s/user/%s/ttyrec/", config.NethackLauncher.HackDir, username) |
||||
exec.Command("mkdir", "-p", userPath).Run() |
||||
|
||||
// copy in rc file
|
||||
hackRCLoc := fmt.Sprintf("%s/.nethackrc", config.NethackLauncher.HackDir) |
||||
hackRCDest := fmt.Sprintf("%s/user/%s/.nethackrc", config.NethackLauncher.HackDir, username) |
||||
exec.Command("cp", hackRCLoc, hackRCDest).Run() |
||||
|
||||
// TODO: move the above creation code into the createUserFiles() function
|
||||
createUserFiles(username) |
||||
|
||||
// back to main screen
|
||||
printUserScreen(redisClient, username) |
||||
} |
||||
} |
||||
if err := scanner.Err(); err != nil { |
||||
fmt.Printf("%s\n", err) |
||||
} |
||||
} |
||||
|
||||
func printChangePasswordScreen(redisClient *redis.Client, username string) { |
||||
// TODO : configure password restriction checking
|
||||
clearScreen() |
||||
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) |
||||
println("") |
||||
fmt.Printf(" Welcome %s\n", 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() == "" {
|
||||
// printUserScreen(redisClient, username)
|
||||
// }
|
||||
|
||||
// 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.") |
||||
} |
||||
} |
||||
|
||||
// reset display
|
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
|
||||
// set user in redis
|
||||
secHash := sha3.Sum512([]byte(sec)) |
||||
redisClient.Set(fmt.Sprintf("user:%s", username), fmt.Sprintf("%x", secHash), 0).Err() |
||||
|
||||
// back to main screen
|
||||
printUserScreen(redisClient, username) |
||||
//}
|
||||
if err := scanner.Err(); err != nil { |
||||
fmt.Printf("%s\n", err) |
||||
} |
||||
} |
||||
|
||||
func printProgressScreen(redisClient *redis.Client, username string) { |
||||
// print header
|
||||
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) |
||||
println("") |
||||
|
||||
// check directory for live players
|
||||
isNotEmpty, err := redisClient.Exists("inprogress").Result() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
if isNotEmpty { |
||||
println(" Choose a player to spectate ('enter' without selection returns)") |
||||
} else { |
||||
println(" No live players currently (blank entry returns)") |
||||
} |
||||
println("") |
||||
|
||||
inProg := make(map[int]string) |
||||
inProgTimer := 1 |
||||
inprogress, err := redisClient.SMembers("inprogress").Result() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
for _, i := range inprogress { |
||||
// loop through users
|
||||
fmt.Printf(" %d) %s\n", inProgTimer, i) |
||||
|
||||
// add user to line map
|
||||
inProg[inProgTimer] = i |
||||
inProgTimer++ |
||||
} |
||||
|
||||
println("") |
||||
fmt.Printf(">> ") |
||||
// start user input
|
||||
|
||||
// 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) |
||||
s, _ := strconv.Atoi(string(b)) |
||||
|
||||
// check if user is trying to navigate
|
||||
if string(b) == "\n" { |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
if username == "" { |
||||
printWelcomeScreen(redisClient) |
||||
} else { |
||||
printUserScreen(redisClient, username) |
||||
} |
||||
} |
||||
|
||||
// check if selection is in out map
|
||||
if inProg[s] != "" { |
||||
user := strings.Split(inProg[s], ":") |
||||
fmt.Printf("going to spectate '%s'\n", user[0]) |
||||
|
||||
// set ttyrec path
|
||||
ttyName, _ := redisClient.Get(fmt.Sprintf("inprogress:%s", user[0])).Result() |
||||
ttyrecPath := fmt.Sprintf("%s/user/%s/ttyrec/%s.ttyrec", config.NethackLauncher.HackDir, user[0], ttyName) |
||||
|
||||
// restart display
|
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
nh := exec.Command("ttyplay", "-p", ttyrecPath) |
||||
//nh := exec.Command("termplay", "-f", "live", ttyrecPath)
|
||||
nh.Stdout = os.Stdout |
||||
nh.Stdin = os.Stdin |
||||
nh.Stderr = os.Stderr |
||||
nh.Run() |
||||
if username == "" { |
||||
printWelcomeScreen(redisClient) |
||||
} else { |
||||
printUserScreen(redisClient, username) |
||||
} |
||||
// TODO fix bug where user has to <ctrl-c> when game exists
|
||||
} |
||||
} |
||||
} |
||||
|
||||
func runGame(username, timestamp string) { |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
|
||||
// put together users home dir
|
||||
homeDir := fmt.Sprintf("%s/user/%s/", config.NethackLauncher.HackDir, username) |
||||
ttyrecPath := fmt.Sprintf("%s/user/%s/ttyrec/%s.ttyrec", config.NethackLauncher.HackDir, username, timestamp) |
||||
|
||||
nh := exec.Command("ttyrec", "-f", ttyrecPath, "--", "nethack") |
||||
nh.Env = os.Environ() |
||||
nh.Env = append(nh.Env, fmt.Sprintf("HOME=%s", homeDir)) |
||||
nh.Env = append(nh.Env, fmt.Sprintf("USER=%s", username)) |
||||
nh.Env = append(nh.Env, fmt.Sprintf("NETHACKDIR=%s/user/%s", config.NethackLauncher.HackDir, username)) |
||||
nh.Stdout = os.Stdout |
||||
nh.Stdin = os.Stdin |
||||
nh.Stderr = os.Stderr |
||||
err := nh.Run() |
||||
if err != nil { |
||||
fmt.Print(err) |
||||
} |
||||
exec.Command("exit").Run() |
||||
wg.Done() |
||||
} |
||||
|
||||
func startWatcher(username, timestamp string, redisClient *redis.Client) chan struct{} { |
||||
// create initial keys
|
||||
redisClient.SAdd("inprogress", username) |
||||
redisClient.Set(fmt.Sprintf("inprogress:%s", username), timestamp, 0) |
||||
|
||||
ch := make(chan struct{}) |
||||
// enter inital inprogress yet
|
||||
go func() { |
||||
for { |
||||
select { |
||||
case <-ch: |
||||
return |
||||
default: |
||||
redisClient.Expire(fmt.Sprintf("inprogress:%s", username), 10*time.Second) |
||||
time.Sleep(8 * time.Second) |
||||
} |
||||
} |
||||
}() |
||||
return ch |
||||
} |
||||
|
||||
func createInitialFiles() { |
||||
// create necessary directories if they dont exist
|
||||
if _, err := os.Stat(config.NethackLauncher.HackDir); os.IsNotExist(err) { |
||||
os.Mkdir(config.NethackLauncher.HackDir, os.ModeDir) |
||||
} |
||||
if _, err := os.Stat(fmt.Sprintf("%s/dumps/", config.NethackLauncher.HackDir)); os.IsNotExist(err) { |
||||
os.Mkdir(fmt.Sprintf("%s/dumps/", config.NethackLauncher.HackDir), os.ModeDir) |
||||
} |
||||
if _, err := os.Stat(fmt.Sprintf("%s/save/", config.NethackLauncher.HackDir)); os.IsNotExist(err) { |
||||
os.Mkdir(fmt.Sprintf("%s/save/", config.NethackLauncher.HackDir), os.ModeDir) |
||||
} |
||||
|
||||
// create necessary files if they dont exist
|
||||
os.OpenFile(fmt.Sprintf("%s/logfile", config.NethackLauncher.HackDir), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/perm", config.NethackLauncher.HackDir), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/record", config.NethackLauncher.HackDir), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/xlogfile", config.NethackLauncher.HackDir), os.O_RDONLY|os.O_CREATE, 0666) |
||||
|
||||
// move in nhdat file if it does not exist
|
||||
if _, err := os.Stat(fmt.Sprintf("%s/nhdat", config.NethackLauncher.HackDir)); os.IsNotExist(err) { |
||||
exec.Command("cp", config.NethackLauncher.NhdatLocation, config.NethackLauncher.HackDir).Run() |
||||
} |
||||
|
||||
// make sure initial rcfile exists
|
||||
hackRCLoc := fmt.Sprintf("%s/.nethackrc", config.NethackLauncher.HackDir) |
||||
if _, err := os.Stat(hackRCLoc); os.IsNotExist(err) { |
||||
fmt.Printf("initial config file not found at: %s\n", hackRCLoc) |
||||
fmt.Printf("%s\n", err) |
||||
|
||||
// check root and move in if applicable
|
||||
if _, err := os.Stat("/.nethackrc"); os.IsNotExist(err) { |
||||
fmt.Printf("initial config file not found at root\n") |
||||
fmt.Printf("%s\n", err) |
||||
os.Exit(1) |
||||
} else { |
||||
// move nethackrc file to proper location
|
||||
exec.Command("cp", "/.nethackrc", config.NethackLauncher.HackDir).Run() |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
func createUserFiles(username string) { |
||||
// create necessary directories if they dont exist
|
||||
userpath := fmt.Sprintf("%s/user/%s", config.NethackLauncher.HackDir, username) |
||||
|
||||
if _, err := os.Stat(fmt.Sprintf("%s/dumps/", userpath)); os.IsNotExist(err) { |
||||
os.Mkdir(fmt.Sprintf("%s/dumps/", userpath), os.ModeDir) |
||||
} |
||||
if _, err := os.Stat(fmt.Sprintf("%s/save/", userpath)); os.IsNotExist(err) { |
||||
os.Mkdir(fmt.Sprintf("%s/save/", userpath), os.ModeDir) |
||||
} |
||||
|
||||
// create necessary files if they dont exist
|
||||
os.OpenFile(fmt.Sprintf("%s/logfile", userpath), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/perm", userpath), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/record", userpath), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/xlogfile", userpath), os.O_RDONLY|os.O_CREATE, 0666) |
||||
|
||||
// move in nhdat file if it does not exist
|
||||
exec.Command("cp", config.NethackLauncher.NhdatLocation, userpath).Run() |
||||
} |
||||
|
||||
func recoverSave(redisClient *redis.Client, username string) { |
||||
// if no statefile exist, exit early as we cannot recover
|
||||
matches, _ := filepath.Glob(fmt.Sprintf("%s/user/%s/*lock*", config.NethackLauncher.HackDir, username)) |
||||
if matches == nil { |
||||
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) |
||||
println("") |
||||
println(" The dungeon crumbles around you, darkness remains...") |
||||
println(" Godspeed adventurer..") |
||||
println("") |
||||
println(" No statefiles exist. (press enter to return)") |
||||
println("") |
||||
fmt.Printf(">> ") |
||||
|
||||
scanner := bufio.NewScanner(os.Stdin) |
||||
for scanner.Scan() { |
||||
if scanner.Text() == "" { |
||||
printUserScreen(redisClient, username) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) |
||||
println("") |
||||
println(" Recover utility.") |
||||
println("") |
||||
// check if dir is empty
|
||||
isEmpty, _ := checkDir(fmt.Sprintf("%s/user/%s/save/", config.NethackLauncher.HackDir, username)) |
||||
if !isEmpty { |
||||
// save file exists, overwrite?
|
||||
println(" Save file already exists!") |
||||
println(" Overwrite? (y/n)") |
||||
println("") |
||||
fmt.Printf(">> ") |
||||
} else { |
||||
println(" Attempt to recover statefile? (y/n)") |
||||
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 "n": |
||||
clearScreen() |
||||
printUserScreen(redisClient, username) |
||||
case "y": |
||||
// set user path to save file candidates
|
||||
userPath := fmt.Sprintf("%s/user/%s/", config.NethackLauncher.HackDir, username) |
||||
|
||||
// read in all files
|
||||
candidates, err := ioutil.ReadDir(userPath) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
} |
||||
var newestFile string |
||||
var newestTime int64 = 0 |
||||
for _, f := range candidates { |
||||
// loop through save file candidates
|
||||
if strings.Contains(f.Name(), "lock") { |
||||
fi, err := os.Stat(userPath + f.Name()) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
} |
||||
currTime := fi.ModTime().Unix() |
||||
if currTime > newestTime { |
||||
newestTime = currTime |
||||
newestFile = f.Name() |
||||
} |
||||
} |
||||
} |
||||
|
||||
// get prefix for latest file
|
||||
latestFile := strings.Split(newestFile, ".") |
||||
//fmt.Printf("recover string: %s -d %s/user/%s %s", config.NethackLauncher.RecoverBinary, config.NethackLauncher.HackDir, username, latestFile[0])
|
||||
|
||||
// run recover on file
|
||||
out, err := exec.Command(config.NethackLauncher.RecoverBinary, "-d", fmt.Sprintf("%s/user/%s", config.NethackLauncher.HackDir, username), latestFile[0]).Output() |
||||
if err != nil { |
||||
fmt.Println(out) |
||||
panic(err) |
||||
} |
||||
|
||||
// make sure save file exists before removing the extra locks
|
||||
isEmpty, _ = checkDir(fmt.Sprintf("%s/user/%s/save/", config.NethackLauncher.HackDir, username)) |
||||
if !isEmpty { |
||||
// save file made it, clean up old cruft
|
||||
files, err := filepath.Glob(fmt.Sprintf("%s/*lock*", userPath)) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
for _, f := range files { |
||||
if err := os.Remove(f); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
||||
// alert user that the save file was recovered
|
||||
println("") |
||||
println(" Statefile was recovered successfully! (press enter to return)") |
||||
fmt.Printf(">> ") |
||||
} |
||||
case "\n": |
||||
clearScreen() |
||||
printUserScreen(redisClient, username) |
||||
default: |
||||
} |
||||
} |
||||
} |
||||
|
||||
func janitor(redisClient *redis.Client) { |
||||
// loop through the set
|
||||
for { |
||||
inprogress, err := redisClient.SMembers("inprogress").Result() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
for _, i := range inprogress { |
||||
// for each user, make sure the expire key exists
|
||||
exists, err := redisClient.Exists(fmt.Sprintf("inprogress:%s", i)).Result() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
if !exists { |
||||
redisClient.SRem("inprogress", i) |
||||
} |
||||
} |
||||
|
||||
time.Sleep(10 * time.Second) |
||||
} |
||||
} |
||||
|
||||
func printHighScores(redisClient *redis.Client, username string) { |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
|
||||
nh := exec.Command("nethack", "-d", config.NethackLauncher.HackDir, "-s") |
||||
nh.Stdout = os.Stdout |
||||
nh.Stdin = os.Stdin |
||||
nh.Stderr = os.Stderr |
||||
nh.Run() |
||||
|
||||
println("") |
||||
println(" Press enter to return to menu") |
||||
println("") |
||||
fmt.Printf(">> ") |
||||
|
||||
// allow user back to home screen
|
||||
// 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) |
||||
|
||||
// check if user is trying to navigate
|
||||
if string(b) == "\n" { |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
if username == "" { |
||||
printWelcomeScreen(redisClient) |
||||
} else { |
||||
printUserScreen(redisClient, username) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
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 |
||||
} |
@ -0,0 +1,20 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"io" |
||||
"os" |
||||
) |
||||
|
||||
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 |
||||
} |
@ -0,0 +1,49 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
) |
||||
|
||||
func createInitialFiles() { |
||||
// create necessary directories if they dont exist
|
||||
if _, err := os.Stat(config.NethackLauncher.HackDir); os.IsNotExist(err) { |
||||
os.Mkdir(config.NethackLauncher.HackDir, os.ModeDir) |
||||
} |
||||
if _, err := os.Stat(fmt.Sprintf("%s/dumps/", config.NethackLauncher.HackDir)); os.IsNotExist(err) { |
||||
os.Mkdir(fmt.Sprintf("%s/dumps/", config.NethackLauncher.HackDir), os.ModeDir) |
||||
} |
||||
if _, err := os.Stat(fmt.Sprintf("%s/save/", config.NethackLauncher.HackDir)); os.IsNotExist(err) { |
||||
os.Mkdir(fmt.Sprintf("%s/save/", config.NethackLauncher.HackDir), os.ModeDir) |
||||
} |
||||
|
||||
// create necessary files if they dont exist
|
||||
os.OpenFile(fmt.Sprintf("%s/logfile", config.NethackLauncher.HackDir), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/perm", config.NethackLauncher.HackDir), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/record", config.NethackLauncher.HackDir), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/xlogfile", config.NethackLauncher.HackDir), os.O_RDONLY|os.O_CREATE, 0666) |
||||
|
||||
// move in nhdat file if it does not exist
|
||||
if _, err := os.Stat(fmt.Sprintf("%s/nhdat", config.NethackLauncher.HackDir)); os.IsNotExist(err) { |
||||
exec.Command("cp", config.NethackLauncher.NhdatLocation, config.NethackLauncher.HackDir).Run() |
||||
} |
||||
|
||||
// make sure initial rcfile exists
|
||||
hackRCLoc := fmt.Sprintf("%s/.nethackrc", config.NethackLauncher.HackDir) |
||||
if _, err := os.Stat(hackRCLoc); os.IsNotExist(err) { |
||||
fmt.Printf("initial config file not found at: %s\n", hackRCLoc) |
||||
fmt.Printf("%s\n", err) |
||||
|
||||
// check root and move in if applicable
|
||||
if _, err := os.Stat("/.nethackrc"); os.IsNotExist(err) { |
||||
fmt.Printf("initial config file not found at root\n") |
||||
fmt.Printf("%s\n", err) |
||||
os.Exit(1) |
||||
} else { |
||||
// move nethackrc file to proper location
|
||||
exec.Command("cp", "/.nethackrc", config.NethackLauncher.HackDir).Run() |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,28 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
) |
||||
|
||||
func createUserFiles(username string) { |
||||
// create necessary directories if they dont exist
|
||||
userpath := fmt.Sprintf("%s/user/%s", config.NethackLauncher.HackDir, username) |
||||
|
||||
if _, err := os.Stat(fmt.Sprintf("%s/dumps/", userpath)); os.IsNotExist(err) { |
||||
os.Mkdir(fmt.Sprintf("%s/dumps/", userpath), os.ModeDir) |
||||
} |
||||
if _, err := os.Stat(fmt.Sprintf("%s/save/", userpath)); os.IsNotExist(err) { |
||||
os.Mkdir(fmt.Sprintf("%s/save/", userpath), os.ModeDir) |
||||
} |
||||
|
||||
// create necessary files if they dont exist
|
||||
os.OpenFile(fmt.Sprintf("%s/logfile", userpath), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/perm", userpath), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/record", userpath), os.O_RDONLY|os.O_CREATE, 0666) |
||||
os.OpenFile(fmt.Sprintf("%s/xlogfile", userpath), os.O_RDONLY|os.O_CREATE, 0666) |
||||
|
||||
// move in nhdat file if it does not exist
|
||||
exec.Command("cp", config.NethackLauncher.NhdatLocation, userpath).Run() |
||||
} |
@ -0,0 +1,30 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"time" |
||||
|
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
func janitor(redisClient *redis.Client) { |
||||
// loop through the set
|
||||
for { |
||||
inprogress, err := redisClient.SMembers("inprogress").Result() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
for _, i := range inprogress { |
||||
// for each user, make sure the expire key exists
|
||||
exists, err := redisClient.Exists(fmt.Sprintf("inprogress:%s", i)).Result() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
if !exists { |
||||
redisClient.SRem("inprogress", i) |
||||
} |
||||
} |
||||
|
||||
time.Sleep(10 * time.Second) |
||||
} |
||||
} |
@ -0,0 +1,104 @@
|
||||
package main |
||||
|
||||
// TODO on runtime "Old lockfile found. Recover/Delete? (r/d)
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
"sync" |
||||
"time" |
||||
|
||||
"gopkg.in/gcfg.v1" |
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
type Config struct { |
||||
NethackLauncher struct { |
||||
Loglevel string |
||||
ServerDisplay string |
||||
NethackVersion string |
||||
HackDir string |
||||
NhdatLocation string |
||||
RecoverBinary string |
||||
BootstrapDelay time.Duration |
||||
} |
||||
|
||||
Redis struct { |
||||
Host string |
||||
Password string |
||||
} |
||||
} |
||||
|
||||
var ( |
||||
config = Config{} |
||||
wg sync.WaitGroup |
||||
) |
||||
|
||||
func main() { |
||||
// read conf file
|
||||
readConf() |
||||
|
||||
// init redis connection
|
||||
redisClient, redisErr := initRedisConnection() |
||||
if redisErr != nil { |
||||
time.Sleep(config.NethackLauncher.BootstrapDelay * time.Second) |
||||
redisClient, redisErr = initRedisConnection() |
||||
if redisErr != nil { |
||||
panic(redisErr) |
||||
} |
||||
} else { |
||||
} |
||||
|
||||
// create initial files needed by nethack
|
||||
createInitialFiles() |
||||
|
||||
// start janitor
|
||||
go janitor(redisClient) |
||||
|
||||
// start homescreen
|
||||
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 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(fmt.Sprintf("%s/record", config.NethackLauncher.HackDir)); os.IsNotExist(err) { |
||||
fmt.Printf("record file not found in %s/record\n", config.NethackLauncher.HackDir) |
||||
fmt.Printf("%s\n", err) |
||||
os.Exit(1) |
||||
} |
||||
// make sure initial rcfile exists
|
||||
hackRCLoc := fmt.Sprintf("%s/.nethackrc", config.NethackLauncher.HackDir) |
||||
if _, err := os.Stat(hackRCLoc); os.IsNotExist(err) { |
||||
fmt.Printf("initial config file not found at: %s\n", hackRCLoc) |
||||
fmt.Printf("%s\n", err) |
||||
os.Exit(1) |
||||
} |
||||
} |
||||
|
||||
func clearScreen() { |
||||
cmd := exec.Command("clear") |
||||
cmd.Stdout = os.Stdout |
||||
cmd.Run() |
||||
} |
@ -0,0 +1,73 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
"strings" |
||||
|
||||
"golang.org/x/crypto/sha3" |
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
func printChangePasswordScreen(redisClient *redis.Client, username string) { |
||||
// TODO : configure password restriction checking
|
||||
clearScreen() |
||||
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) |
||||
println("") |
||||
fmt.Printf(" Welcome %s\n", 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() == "" {
|
||||
// printUserScreen(redisClient, username)
|
||||
// }
|
||||
|
||||
// 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.") |
||||
} |
||||
} |
||||
|
||||
// reset display
|
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
|
||||
// set user in redis
|
||||
secHash := sha3.Sum512([]byte(sec)) |
||||
redisClient.Set(fmt.Sprintf("user:%s", username), fmt.Sprintf("%x", secHash), 0).Err() |
||||
|
||||
// back to main screen
|
||||
printUserScreen(redisClient, username) |
||||
//}
|
||||
if err := scanner.Err(); err != nil { |
||||
fmt.Printf("%s\n", err) |
||||
} |
||||
} |
@ -0,0 +1,47 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
|
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
func printHighScores(redisClient *redis.Client, username string) { |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
|
||||
nh := exec.Command("nethack", "-d", config.NethackLauncher.HackDir, "-s") |
||||
nh.Stdout = os.Stdout |
||||
nh.Stdin = os.Stdin |
||||
nh.Stderr = os.Stderr |
||||
nh.Run() |
||||
|
||||
println("") |
||||
println(" Press enter to return to menu") |
||||
println("") |
||||
fmt.Printf(">> ") |
||||
|
||||
// allow user back to home screen
|
||||
// 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) |
||||
|
||||
// check if user is trying to navigate
|
||||
if string(b) == "\n" { |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
if username == "" { |
||||
printWelcomeScreen(redisClient) |
||||
} else { |
||||
printUserScreen(redisClient, username) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,66 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
"strings" |
||||
|
||||
"golang.org/x/crypto/sha3" |
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
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) |
||||
} |
||||
} |
@ -0,0 +1,96 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
func printProgressScreen(redisClient *redis.Client, username string) { |
||||
// print header
|
||||
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) |
||||
println("") |
||||
|
||||
// check directory for live players
|
||||
isNotEmpty, err := redisClient.Exists("inprogress").Result() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
if isNotEmpty { |
||||
println(" Choose a player to spectate ('enter' without selection returns)") |
||||
} else { |
||||
println(" No live players currently (blank entry returns)") |
||||
} |
||||
println("") |
||||
|
||||
inProg := make(map[int]string) |
||||
inProgTimer := 1 |
||||
inprogress, err := redisClient.SMembers("inprogress").Result() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
for _, i := range inprogress { |
||||
// loop through users
|
||||
fmt.Printf(" %d) %s\n", inProgTimer, i) |
||||
|
||||
// add user to line map
|
||||
inProg[inProgTimer] = i |
||||
inProgTimer++ |
||||
} |
||||
|
||||
println("") |
||||
fmt.Printf(">> ") |
||||
// start user input
|
||||
|
||||
// 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) |
||||
s, _ := strconv.Atoi(string(b)) |
||||
|
||||
// check if user is trying to navigate
|
||||
if string(b) == "\n" { |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
if username == "" { |
||||
printWelcomeScreen(redisClient) |
||||
} else { |
||||
printUserScreen(redisClient, username) |
||||
} |
||||
} |
||||
|
||||
// check if selection is in out map
|
||||
if inProg[s] != "" { |
||||
user := strings.Split(inProg[s], ":") |
||||
fmt.Printf("going to spectate '%s'\n", user[0]) |
||||
|
||||
// set ttyrec path
|
||||
ttyName, _ := redisClient.Get(fmt.Sprintf("inprogress:%s", user[0])).Result() |
||||
ttyrecPath := fmt.Sprintf("%s/user/%s/ttyrec/%s.ttyrec", config.NethackLauncher.HackDir, user[0], ttyName) |
||||
|
||||
// restart display
|
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
nh := exec.Command("ttyplay", "-p", ttyrecPath) |
||||
//nh := exec.Command("termplay", "-f", "live", ttyrecPath)
|
||||
nh.Stdout = os.Stdout |
||||
nh.Stdin = os.Stdin |
||||
nh.Stderr = os.Stderr |
||||
nh.Run() |
||||
if username == "" { |
||||
printWelcomeScreen(redisClient) |
||||
} else { |
||||
printUserScreen(redisClient, username) |
||||
} |
||||
// TODO fix bug where user has to <ctrl-c> when game exists
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,94 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
"strings" |
||||
|
||||
"golang.org/x/crypto/sha3" |
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
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.") |
||||
} |
||||
} |
||||
|
||||
// reset display
|
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
|
||||
// set user in redis
|
||||
secHash := sha3.Sum512([]byte(sec)) |
||||
redisClient.Set(fmt.Sprintf("user:%s", username), fmt.Sprintf("%x", secHash), 0).Err() |
||||
|
||||
// create user directories
|
||||
userPath := fmt.Sprintf("%s/user/%s/ttyrec/", config.NethackLauncher.HackDir, username) |
||||
exec.Command("mkdir", "-p", userPath).Run() |
||||
|
||||
// copy in rc file
|
||||
hackRCLoc := fmt.Sprintf("%s/.nethackrc", config.NethackLauncher.HackDir) |
||||
hackRCDest := fmt.Sprintf("%s/user/%s/.nethackrc", config.NethackLauncher.HackDir, username) |
||||
exec.Command("cp", hackRCLoc, hackRCDest).Run() |
||||
|
||||
// TODO: move the above creation code into the createUserFiles() function
|
||||
createUserFiles(username) |
||||
|
||||
// back to main screen
|
||||
printUserScreen(redisClient, username) |
||||
} |
||||
} |
||||
if err := scanner.Err(); err != nil { |
||||
fmt.Printf("%s\n", err) |
||||
} |
||||
} |
@ -0,0 +1,82 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
"time" |
||||
|
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
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(" c) Change password") |
||||
println(" w) Watch games in progress") |
||||
println(" h) View highscores") |
||||
println(" e) Edit config") |
||||
println(" r) Recover from crash") |
||||
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 "e": |
||||
hackRCLoc := fmt.Sprintf("%s/user/%s/.nethackrc", config.NethackLauncher.HackDir, username) |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
nh := exec.Command("vim", "-Z", hackRCLoc) |
||||
nh.Stdout = os.Stdout |
||||
nh.Stdin = os.Stdin |
||||
nh.Stderr = os.Stderr |
||||
nh.Run() |
||||
clearScreen() |
||||
printUserScreen(redisClient, username) |
||||
case "c": |
||||
printChangePasswordScreen(redisClient, username) |
||||
clearScreen() |
||||
case "w": |
||||
clearScreen() |
||||
printProgressScreen(redisClient, username) |
||||
case "h": |
||||
clearScreen() |
||||
printHighScores(redisClient, username) |
||||
case "p": |
||||
wg.Add(1) |
||||
currentTime := time.Now().UTC() |
||||
fulltime := currentTime.Format("2006-01-02.03:04:05") |
||||
go runGame(username, fulltime) |
||||
watcher := startWatcher(username, fulltime, redisClient) |
||||
wg.Wait() |
||||
close(watcher) |
||||
printUserScreen(redisClient, username) |
||||
case "r": |
||||
clearScreen() |
||||
recoverSave(redisClient, username) |
||||
case "q": |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
os.Exit(0) |
||||
default: |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,57 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
|
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
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(" h) View highscores") |
||||
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(redisClient, "") |
||||
case "h": |
||||
clearScreen() |
||||
printHighScores(redisClient, "") |
||||
case "q": |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
os.Exit(0) |
||||
default: |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,126 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
func recoverSave(redisClient *redis.Client, username string) { |
||||
// if no statefile exist, exit early as we cannot recover
|
||||
matches, _ := filepath.Glob(fmt.Sprintf("%s/user/%s/*lock*", config.NethackLauncher.HackDir, username)) |
||||
if matches == nil { |
||||
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) |
||||
println("") |
||||
println(" The dungeon crumbles around you, darkness remains...") |
||||
println(" Godspeed adventurer..") |
||||
println("") |
||||
println(" No statefiles exist. (press enter to return)") |
||||
println("") |
||||
fmt.Printf(">> ") |
||||
|
||||
scanner := bufio.NewScanner(os.Stdin) |
||||
for scanner.Scan() { |
||||
if scanner.Text() == "" { |
||||
printUserScreen(redisClient, username) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) |
||||
println("") |
||||
println(" Recover utility.") |
||||
println("") |
||||
// check if dir is empty
|
||||
isEmpty, _ := checkDir(fmt.Sprintf("%s/user/%s/save/", config.NethackLauncher.HackDir, username)) |
||||
if !isEmpty { |
||||
// save file exists, overwrite?
|
||||
println(" Save file already exists!") |
||||
println(" Overwrite? (y/n)") |
||||
println("") |
||||
fmt.Printf(">> ") |
||||
} else { |
||||
println(" Attempt to recover statefile? (y/n)") |
||||
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 "n": |
||||
clearScreen() |
||||
printUserScreen(redisClient, username) |
||||
case "y": |
||||
// set user path to save file candidates
|
||||
userPath := fmt.Sprintf("%s/user/%s/", config.NethackLauncher.HackDir, username) |
||||
|
||||
// read in all files
|
||||
candidates, err := ioutil.ReadDir(userPath) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
} |
||||
var newestFile string |
||||
var newestTime int64 = 0 |
||||
for _, f := range candidates { |
||||
// loop through save file candidates
|
||||
if strings.Contains(f.Name(), "lock") { |
||||
fi, err := os.Stat(userPath + f.Name()) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
} |
||||
currTime := fi.ModTime().Unix() |
||||
if currTime > newestTime { |
||||
newestTime = currTime |
||||
newestFile = f.Name() |
||||
} |
||||
} |
||||
} |
||||
|
||||
// get prefix for latest file
|
||||
latestFile := strings.Split(newestFile, ".") |
||||
|
||||
// run recover on file
|
||||
out, err := exec.Command(config.NethackLauncher.RecoverBinary, "-d", fmt.Sprintf("%s/user/%s", config.NethackLauncher.HackDir, username), latestFile[0]).Output() |
||||
if err != nil { |
||||
fmt.Println(out) |
||||
panic(err) |
||||
} |
||||
|
||||
// make sure save file exists before removing the extra locks
|
||||
isEmpty, _ = checkDir(fmt.Sprintf("%s/user/%s/save/", config.NethackLauncher.HackDir, username)) |
||||
if !isEmpty { |
||||
// save file made it, clean up old cruft
|
||||
files, err := filepath.Glob(fmt.Sprintf("%s/*lock*", userPath)) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
for _, f := range files { |
||||
if err := os.Remove(f); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
||||
// alert user that the save file was recovered
|
||||
println("") |
||||
println(" Statefile was recovered successfully! (press enter to return)") |
||||
fmt.Printf(">> ") |
||||
} |
||||
case "\n": |
||||
clearScreen() |
||||
printUserScreen(redisClient, username) |
||||
default: |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,31 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
) |
||||
|
||||
func runGame(username, timestamp string) { |
||||
exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() |
||||
clearScreen() |
||||
|
||||
// put together users home dir
|
||||
homeDir := fmt.Sprintf("%s/user/%s/", config.NethackLauncher.HackDir, username) |
||||
ttyrecPath := fmt.Sprintf("%s/user/%s/ttyrec/%s.ttyrec", config.NethackLauncher.HackDir, username, timestamp) |
||||
|
||||
nh := exec.Command("ttyrec", "-f", ttyrecPath, "--", "nethack") |
||||
nh.Env = os.Environ() |
||||
nh.Env = append(nh.Env, fmt.Sprintf("HOME=%s", homeDir)) |
||||
nh.Env = append(nh.Env, fmt.Sprintf("USER=%s", username)) |
||||
nh.Env = append(nh.Env, fmt.Sprintf("NETHACKDIR=%s/user/%s", config.NethackLauncher.HackDir, username)) |
||||
nh.Stdout = os.Stdout |
||||
nh.Stdin = os.Stdin |
||||
nh.Stderr = os.Stderr |
||||
err := nh.Run() |
||||
if err != nil { |
||||
fmt.Print(err) |
||||
} |
||||
exec.Command("exit").Run() |
||||
wg.Done() |
||||
} |
@ -0,0 +1,29 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"time" |
||||
|
||||
"gopkg.in/redis.v5" |
||||
) |
||||
|
||||
func startWatcher(username, timestamp string, redisClient *redis.Client) chan struct{} { |
||||
// create initial keys
|
||||
redisClient.SAdd("inprogress", username) |
||||
redisClient.Set(fmt.Sprintf("inprogress:%s", username), timestamp, 0) |
||||
|
||||
ch := make(chan struct{}) |
||||
// enter inital inprogress yet
|
||||
go func() { |
||||
for { |
||||
select { |
||||
case <-ch: |
||||
return |
||||
default: |
||||
redisClient.Expire(fmt.Sprintf("inprogress:%s", username), 10*time.Second) |
||||
time.Sleep(8 * time.Second) |
||||
} |
||||
} |
||||
}() |
||||
return ch |
||||
} |
Loading…
Reference in new issue