Browse Source

Add recovery utility

master
Matthew Faltys 5 years ago
parent
commit
f3e30415be
  1. 1
      config.gcfg
  2. 128
      deps/nethackrc
  3. 166
      deps/nethackrc.full
  4. 1
      deps/server_config.gcfg
  5. 150
      nethack-launcher.go

1
config.gcfg

@ -4,6 +4,7 @@
nethackversion = "3.6.1"
hackdir = "savedata/hack"
nhdatlocation = "/usr/lib/games/nethack/nhdat"
recoverbinary = "/usr/lib/games/nethack/recover"
bootstrapdelay = 1
[redis]

128
deps/nethackrc vendored

@ -2,13 +2,11 @@
# good luck escaping
# jk, its vim
#OPTIONS=showexp,showscore,time,color,!autopickup
#OPTIONS=autodig,fruit:slime mold,boulder:0
# standard options
OPTIONS=menucolors
OPTIONS=autodig
OPTIONS=autoopen
#OPTIONS=autopickup,pickup_types:$!/="+?
OPTIONS=autopickup,pickup_types:$!/="+?
OPTIONS=autoquiver
OPTIONS=bones
OPTIONS=boulder:0
@ -20,20 +18,23 @@ OPTIONS=hilite_pet
OPTIONS=hilite_pile
OPTIONS=msg_window:reverse,msghistory:1000
OPTIONS=lit_corridor
# statusbar options
OPTIONS=showrace
OPTIONS=showexp,time
OPTIONS=sortloot:full
# curses interface
#OPTIONS=windowtype:curses
#OPTIONS=perm_invent
## Change symbols
SYMBOLS=S_ghost:X
SYMBOLS=S_mimic_def:m
SYMBOLS=S_water:~
SYMBOLS=S_lava:~
###
# COLORS
###
## Use status hilites
OPTIONS=statushilites
# HP
## HP
OPTIONS=hitpointbar
OPTIONS=hilite_status:hitpoints/100%/grey&normal
OPTIONS=hilite_status:hitpoints/<100%/green&normal
@ -59,108 +60,3 @@ OPTIONS=hilite_status:condition/stun/red&bold
OPTIONS=hilite_status:condition/termill/red&inverse
OPTIONS=hilite_status:condition/foodpois/red&inverse
OPTIONS=hilite_status:condition/slime/red&inverse
# Str, Con, Dex, Int, Wis, Cha
OPTIONS=hilite_status:characteristics/up/green/down/red
# gold
OPTIONS=hilite_status: gold/up/yellow/down/brown
# FORCE COLORS
MENUCOLOR="\{R\}"=red
MENUCOLOR="\{B\}"=blue
MENUCOLOR="\{C\}"=cyan
MENUCOLOR="\{G\}"=green
MENUCOLOR="\{Y\}"=yellow
MENUCOLOR="\{[MP]\}"=magenta
MENUCOLOR="\{O\}"=orange
MENUCOLOR="\{W\}"=white
MENUCOLOR="\{L\}"=black
MENUCOLOR="\{y\}"=gray
MENUCOLOR="\{g\}"=lightgreen
MENUCOLOR="\{b\}"=lightblue
MENUCOLOR="\{[mp]\}"=lightmagenta
MENUCOLOR="\{c\}"=lightcyan
# GOODIES
MENUCOLOR=" bag .* holding"=magenta
MENUCOLOR=" luck(stone)?($| )"=magenta
MENUCOLOR=" wand .* wish(ing)?($| )"=magenta
MENUCOLOR=" wand .* tele(port(ation)?)?($| )"=magenta
MENUCOLOR=" wand .* polymorph"=magenta
MENUCOLOR=" wand .* death"=magenta
MENUCOLOR=" gain level"=magenta
MENUCOLOR=" full healing"=magenta
MENUCOLOR=" magic marker"=magenta
MENUCOLOR=" magic lamp|lamp .* magic"=magenta
MENUCOLOR=" unicorn horn[^[]*$"=magenta
#this doesn't color the #enhance unicorn
MENUCOLOR=" tinning kit"=magenta
MENUCOLOR=" ring .* regen(eration)?($| )"=magenta
MENUCOLOR=" ring .* conflict"=magenta
MENUCOLOR=" ring .* (FA|free action)($| )"=magenta
MENUCOLOR=" ring .* (TC|teleport control)($| )"=magenta
MENUCOLOR=" ring .* lev(itation)?($| )"=magenta
MENUCOLOR=" scrolls? .* genocide($| )"=magenta
MENUCOLOR=" scrolls? .* charging($| )"=magenta
MENUCOLOR=" scrolls? .* identify($| )"=magenta
MENUCOLOR=" amulet .* (life ?saving|LS)($| )"=magenta
MENUCOLOR=" amulet .* ref(lection)?($| )"=magenta
MENUCOLOR=" c(o|hi)ckatrice (corpse|egg)"=magenta
MENUCOLOR=" egg .* cockatrice"=magenta
MENUCOLOR=" stethoscope"=magenta
#instruments
MENUCOLOR="tooled horn|fire horn|frost horn|horn .* (tooled|fire|frost)"=magenta
MENUCOLOR=" harp"=magenta
MENUCOLOR=" bugle"=magenta
MENUCOLOR=" flute"=magenta
# unidentified magic armor
MENUCOLOR="piece of cloth|opera cloak|ornamental cope|tattered cape"=magenta
MENUCOLOR="plumed helmet|etched helmet|crested helmet|visored helmet"=magenta
MENUCOLOR="(old|padded|riding|fencing) gloves"=magenta
MENUCOLOR="(mud|buckled|riding|snow|hiking|combat|jungle) boots"=magenta
# FOOD CONDUCTS
# vegan
MENUCOLOR="(food |cram |K-|C-)ration|gunyoki"=green&bold
MENUCOLOR="lembas wafer|melon|carrot|tins? .* spinach"=green&bold
MENUCOLOR=" oranges?( named.*)?$"=green&bold
# avoid coloring orange DSM
MENUCOLOR=" pears?( named.*)?$"=green&bold
# avoid coloring pearl rings
MENUCOLOR=" (apple|banana|kelp|eucalyptus|garlic|wolfsbane)"=green&bold
MENUCOLOR=" (slime mold|berries)"=green&bold
## YOUR FRUIT HERE
MENUCOLOR="(tins? of )?(gelatinous cube|acid blob|quivering blob)( corpse)?"=green&bold
MENUCOLOR="(tins? of )?(blue|spotted|ochre) jelly( corpse)?"=green&bold
MENUCOLOR="(tins? of )?lichen|shrieker|violet fungus|(brown|yellow|green|red) mold( corpse)?"=green&bold
# vegetarian
MENUCOLOR="egg|pancake|fortune cookie|candy bar|royal jelly|cream pie|candle"=green
MENUCOLOR="(tin of )?brown pudding|gray ooze( corpse)?"=green
# WATER SPORTS
MENUCOLOR=" holy water"=cyan&bold
MENUCOLOR=" blessed clear potion"=cyan&bold
MENUCOLOR=" blessed potions? called water"=cyan&bold
MENUCOLOR=" clear potions? named (holy|blessed|B)($| )"=cyan&bold
MENUCOLOR=" potions? of water named (holy|blessed|B)($| )"=cyan&bold
MENUCOLOR=" potions? called water named (holy|blessed|B)($| )"=cyan&bold
MENUCOLOR=" unholy water"=orange
MENUCOLOR=" cursed clear potion"=orange
MENUCOLOR=" cursed potions? called water"=orange
MENUCOLOR=" potions? called water named (unholy|cursed|C)($| )"=orange
# B/U/C
#MENUCOLOR="^[a-zA-Z$] - "=white #may want to enable this if regularly playing priest.. (priests don't see the 'uncursed')
MENUCOLOR=" !C"=white
MENUCOLOR=" !B"=white
MENUCOLOR=" !UC"=white
MENUCOLOR=" uncursed| UC?($| )"=white
MENUCOLOR=" \([-0-9]+:[-0-9]+\)"=white
#nethack doesn't display "uncursed" if charges are known
MENUCOLOR=" [+-][0-9]"=white
MENUCOLOR=" blessed| B($| )"=cyan
MENUCOLOR=" cursed| C($| )"=red

166
deps/nethackrc.full vendored

@ -0,0 +1,166 @@
# welcome to the real unixvoid dungeon
# good luck escaping
# jk, its vim
#OPTIONS=showexp,showscore,time,color,!autopickup
#OPTIONS=autodig,fruit:slime mold,boulder:0
OPTIONS=menucolors
OPTIONS=autodig
OPTIONS=autoopen
OPTIONS=autopickup,pickup_types:$!/="+?
OPTIONS=autoquiver
OPTIONS=bones
OPTIONS=boulder:0
OPTIONS=checkpoint
OPTIONS=color
OPTIONS=confirm
OPTIONS=dark_room
OPTIONS=hilite_pet
OPTIONS=hilite_pile
OPTIONS=msg_window:reverse,msghistory:1000
OPTIONS=lit_corridor
OPTIONS=showrace
OPTIONS=showexp,time
OPTIONS=sortloot:full
## Change symbols
SYMBOLS=S_ghost:X
SYMBOLS=S_mimic_def:m
SYMBOLS=S_water:~
SYMBOLS=S_lava:~
## Use status hilites
OPTIONS=statushilites
# HP
OPTIONS=hitpointbar
OPTIONS=hilite_status:hitpoints/100%/grey&normal
OPTIONS=hilite_status:hitpoints/<100%/green&normal
OPTIONS=hilite_status:hitpoints/<66%/yellow&normal
OPTIONS=hilite_status:hitpoints/<50%/orange&normal
OPTIONS=hilite_status:hitpoints/<33%/red&bold
OPTIONS=hilite_status:hitpoints/<15%/red&inverse
## Pw
OPTIONS=hilite_status:power/100%/grey&normal
OPTIONS=hilite_status:power/<100%/green&normal
OPTIONS=hilite_status:power/<66%/yellow&normal
OPTIONS=hilite_status:power/<50%/orange&normal
OPTIONS=hilite_status:power/<33%/red&bold
## Carry
OPTIONS=hilite_status:cap/burdened/yellow/stressed/orange/strained/red&bold/overtaxed/red&inverse/overloaded/red&inverse&blink
## Hunger
OPTIONS=hilite_status:hunger/satiated/yellow/hungry/orange/weak/red&bold/fainting/red&inverse/fainted/red&inverse&blink
## Mental
OPTIONS=hilite_status:condition/hallu/yellow
OPTIONS=hilite_status:condition/conf/orange
OPTIONS=hilite_status:condition/stun/red&bold
## Health
OPTIONS=hilite_status:condition/termill/red&inverse
OPTIONS=hilite_status:condition/foodpois/red&inverse
OPTIONS=hilite_status:condition/slime/red&inverse
# Str, Con, Dex, Int, Wis, Cha
OPTIONS=hilite_status:characteristics/up/green/down/red
# gold
OPTIONS=hilite_status: gold/up/yellow/down/brown
# FORCE COLORS
MENUCOLOR="\{R\}"=red
MENUCOLOR="\{B\}"=blue
MENUCOLOR="\{C\}"=cyan
MENUCOLOR="\{G\}"=green
MENUCOLOR="\{Y\}"=yellow
MENUCOLOR="\{[MP]\}"=magenta
MENUCOLOR="\{O\}"=orange
MENUCOLOR="\{W\}"=white
MENUCOLOR="\{L\}"=black
MENUCOLOR="\{y\}"=gray
MENUCOLOR="\{g\}"=lightgreen
MENUCOLOR="\{b\}"=lightblue
MENUCOLOR="\{[mp]\}"=lightmagenta
MENUCOLOR="\{c\}"=lightcyan
# GOODIES
MENUCOLOR=" bag .* holding"=magenta
MENUCOLOR=" luck(stone)?($| )"=magenta
MENUCOLOR=" wand .* wish(ing)?($| )"=magenta
MENUCOLOR=" wand .* tele(port(ation)?)?($| )"=magenta
MENUCOLOR=" wand .* polymorph"=magenta
MENUCOLOR=" wand .* death"=magenta
MENUCOLOR=" gain level"=magenta
MENUCOLOR=" full healing"=magenta
MENUCOLOR=" magic marker"=magenta
MENUCOLOR=" magic lamp|lamp .* magic"=magenta
MENUCOLOR=" unicorn horn[^[]*$"=magenta
#this doesn't color the #enhance unicorn
MENUCOLOR=" tinning kit"=magenta
MENUCOLOR=" ring .* regen(eration)?($| )"=magenta
MENUCOLOR=" ring .* conflict"=magenta
MENUCOLOR=" ring .* (FA|free action)($| )"=magenta
MENUCOLOR=" ring .* (TC|teleport control)($| )"=magenta
MENUCOLOR=" ring .* lev(itation)?($| )"=magenta
MENUCOLOR=" scrolls? .* genocide($| )"=magenta
MENUCOLOR=" scrolls? .* charging($| )"=magenta
MENUCOLOR=" scrolls? .* identify($| )"=magenta
MENUCOLOR=" amulet .* (life ?saving|LS)($| )"=magenta
MENUCOLOR=" amulet .* ref(lection)?($| )"=magenta
MENUCOLOR=" c(o|hi)ckatrice (corpse|egg)"=magenta
MENUCOLOR=" egg .* cockatrice"=magenta
MENUCOLOR=" stethoscope"=magenta
#instruments
MENUCOLOR="tooled horn|fire horn|frost horn|horn .* (tooled|fire|frost)"=magenta
MENUCOLOR=" harp"=magenta
MENUCOLOR=" bugle"=magenta
MENUCOLOR=" flute"=magenta
# unidentified magic armor
MENUCOLOR="piece of cloth|opera cloak|ornamental cope|tattered cape"=magenta
MENUCOLOR="plumed helmet|etched helmet|crested helmet|visored helmet"=magenta
MENUCOLOR="(old|padded|riding|fencing) gloves"=magenta
MENUCOLOR="(mud|buckled|riding|snow|hiking|combat|jungle) boots"=magenta
# FOOD CONDUCTS
# vegan
MENUCOLOR="(food |cram |K-|C-)ration|gunyoki"=green&bold
MENUCOLOR="lembas wafer|melon|carrot|tins? .* spinach"=green&bold
MENUCOLOR=" oranges?( named.*)?$"=green&bold
# avoid coloring orange DSM
MENUCOLOR=" pears?( named.*)?$"=green&bold
# avoid coloring pearl rings
MENUCOLOR=" (apple|banana|kelp|eucalyptus|garlic|wolfsbane)"=green&bold
MENUCOLOR=" (slime mold|berries)"=green&bold
## YOUR FRUIT HERE
MENUCOLOR="(tins? of )?(gelatinous cube|acid blob|quivering blob)( corpse)?"=green&bold
MENUCOLOR="(tins? of )?(blue|spotted|ochre) jelly( corpse)?"=green&bold
MENUCOLOR="(tins? of )?lichen|shrieker|violet fungus|(brown|yellow|green|red) mold( corpse)?"=green&bold
# vegetarian
MENUCOLOR="egg|pancake|fortune cookie|candy bar|royal jelly|cream pie|candle"=green
MENUCOLOR="(tin of )?brown pudding|gray ooze( corpse)?"=green
# WATER SPORTS
MENUCOLOR=" holy water"=cyan&bold
MENUCOLOR=" blessed clear potion"=cyan&bold
MENUCOLOR=" blessed potions? called water"=cyan&bold
MENUCOLOR=" clear potions? named (holy|blessed|B)($| )"=cyan&bold
MENUCOLOR=" potions? of water named (holy|blessed|B)($| )"=cyan&bold
MENUCOLOR=" potions? called water named (holy|blessed|B)($| )"=cyan&bold
MENUCOLOR=" unholy water"=orange
MENUCOLOR=" cursed clear potion"=orange
MENUCOLOR=" cursed potions? called water"=orange
MENUCOLOR=" potions? called water named (unholy|cursed|C)($| )"=orange
# B/U/C
#MENUCOLOR="^[a-zA-Z$] - "=white #may want to enable this if regularly playing priest.. (priests don't see the 'uncursed')
MENUCOLOR=" !C"=white
MENUCOLOR=" !B"=white
MENUCOLOR=" !UC"=white
MENUCOLOR=" uncursed| UC?($| )"=white
MENUCOLOR=" \([-0-9]+:[-0-9]+\)"=white
#nethack doesn't display "uncursed" if charges are known
MENUCOLOR=" [+-][0-9]"=white
MENUCOLOR=" blessed| B($| )"=cyan
MENUCOLOR=" cursed| C($| )"=red

1
deps/server_config.gcfg vendored

@ -4,6 +4,7 @@
nethackversion = "3.6.1"
hackdir = "/hack"
nhdatlocation = "/usr/lib/games/nethack/nhdat"
recoverbinary = "/usr/lib/games/nethack/recover"
bootstrapdelay = 1
[redis]

150
nethack-launcher.go

@ -1,14 +1,15 @@
package main
// TODO make poll time faster
// TODO password redo field
// TODO split up into multiple files
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
@ -26,6 +27,7 @@ type Config struct {
NethackVersion string
HackDir string
NhdatLocation string
RecoverBinary string
BootstrapDelay time.Duration
}
@ -167,6 +169,7 @@ func printUserScreen(redisClient *redis.Client, username string) string {
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("")
@ -215,6 +218,9 @@ func printUserScreen(redisClient *redis.Client, username string) string {
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()
@ -348,6 +354,9 @@ func printRegisterScreen(redisClient *redis.Client) {
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)
}
@ -515,7 +524,7 @@ func runGame(username, timestamp string) {
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", config.NethackLauncher.HackDir))
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
@ -590,6 +599,141 @@ func createInitialFiles() {
}
}
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 utilty.")
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 recovery 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 {

Loading…
Cancel
Save