예제 #1
0
  def run(self, player, id):
    if not player:
      return self.list_players()
    elif not doomrl.user_exists(player):
      return 'No such player: %s' % player
    elif not id:
      return doomrl.show_scores(self.mortems(player))

    mortems = self.mortems(player)
    if id == "latest":
      id = mortems[-1]['n']
    else:
      try:
        id = int(id)
      except:
        return 'Invalid mortem ID.'

    mortem = doomrl.homepath('archive', '%d.mortem' % id, user=player)
    if not exists(mortem):
      return 'No mortem with that ID found for that player.'

    try:
      subprocess.call(
        ['less', '-d', '-M', mortem],
        cwd=doomrl.homepath('archive', user=player),
        env={**os.environ, 'LESSSECURE': '1'})
    except KeyboardInterrupt:
      pass
예제 #2
0
    def run(self, player, id):
        if not player:
            return self.list_players()
        elif not doomrl.user_exists(player):
            return 'No such player: %s' % player
        elif not id:
            return doomrl.show_scores(self.mortems(player))

        mortems = self.mortems(player)
        if id == "latest":
            id = mortems[-1]['n']
        else:
            try:
                id = int(id)
            except:
                return 'Invalid mortem ID.'

        mortem = doomrl.homepath('archive', '%d.mortem' % id, user=player)
        if not exists(mortem):
            return 'No mortem with that ID found for that player.'

        try:
            subprocess.call(['less', '-d', '-M', mortem],
                            cwd=doomrl.homepath('archive', user=player),
                            env={
                                **os.environ, 'LESSSECURE': '1'
                            })
        except KeyboardInterrupt:
            pass
예제 #3
0
  def install(self, password):
    """Install a personal copy of DoomRL to the given path.

    Note that the installed copy is not complete; it is missing server
    configuration files and the symlink to the doomrl binary itself. The reason
    for this is that the location of these files may change across runs (e.g.
    if DoomRL is reinstalled to a different location, or if you're running on
    NixOS and doomrl-server is updated), so they are symlinked anew each time
    the 'play' command is used. Look in play.py for that part.
    """

    # Copy in the user-editable config files.
    for file in ['colours.lua', 'controls.lua', 'user.lua']:
      shutil.copy(doomrl.datapath('config', file), doomrl.homepath(file))
      os.chmod(doomrl.homepath(file), S_IWUSR + S_IRUSR)

    # Create directories for save files, postmortems, etc.
    for dir in ['backup', 'mortem', 'screenshot', 'saves', 'archive']:
      os.mkdir(doomrl.homepath(dir))

    # Write the password file.
    # No hashing or anything. This whole thing is fundamentally insecure, adding
    # a hash would just make it less obviously insecure without actually fixing
    # anything.
    with open(doomrl.homepath('passwd'), 'w') as passwd:
      passwd.write(password)
예제 #4
0
  def run(self, player):
    if not player:
      # list active games
      print('   TIME PLAYER')
      for player in doomrl.all_users():
        if exists(doomrl.homepath('ttyrec', user=player)):
          with TTYRec(path=doomrl.homepath('ttyrec', user=player)) as ttyrec:
            print("%7s %s" % (str(timedelta(seconds=int(ttyrec.ttytime()[0]))), player))
      return

    if not exists(doomrl.homepath('ttyrec', user=player)):
      return 'No game in progress under that name.'

    with TTYRec(doomrl.homepath('ttyrec', user=player)) as ttyrec:
      ttyrec.ttyplay(follow=True)
예제 #5
0
  def setup(self, name):
    # If the player has a game in progress, restore their ttyrec and save.
    if exists(doomrl.homepath('saves', name + '.ttyrec')):
      os.rename(doomrl.homepath('saves', name + '.ttyrec'), self.recfile)
      print('Recording of current game restored.')
    if exists(doomrl.homepath('saves', name)):
      os.rename(doomrl.homepath('saves', name), doomrl.homepath('save'))

    # Get their pre-game high scores so we can check for new ones afterwards.
    scores_before = doomrl.raw_scores()
    try:
      mortem_before = doomrl.raw_mortems()[-1]
    except IndexError:
      mortem_before = None

    return (scores_before,mortem_before)
예제 #6
0
  def run(self, file, reset):
    if not doomrl.user():
      return 'You must be logged in.'

    if reset and reset != 'reset':
      return 'Invalid second argument. See "help config".'

    files = frozenset(['controls', 'colours', 'user'])

    if reset and file == "all":
      for file in files:
        reset(file)
      return

    if not file or file not in files:
      print('Try "config <controls|colours|user>" or "help config".')
      return

    if reset:
      reset(file)
      return

    # Use Nano in secure mode to edit the file.
    subprocess.call(
      ['nano', '-R', doomrl.homepath('%s.lua' % file)])
예제 #7
0
  def run_doomrl(self):
    if exists(doomrl.datapath('ttysound', 'libSDL_mixer-1.2.so.0')):
      cmd = ['./doomrl']
      env = {
        **os.environ,
        "LD_LIBRARY_PATH": doomrl.datapath('ttysound'),
        "SDL_AUDIODRIVER": 'disk',
        'SDL_DISKAUDIOFILE': '/dev/null',
      }
    else:
      cmd = ['./doomrl', '-nosound']
      env = os.environ

    (rpipe,wpipe) = os.pipe()
    self.child = subprocess.Popen(cmd, stdout=wpipe, env=env, cwd=doomrl.homepath())
    os.close(wpipe)

    # DoomRL needs the terminal in raw mode, but since it's running inside ttyrec
    # it can't control that setting.
    tty.setraw(1)

    with TTYRec(self.recfile) as rec:
      # This will return when DoomRL closes the fd.
      # It will also automatically reset the TTY on leaving the 'with' block.
      rec.ttyrec(in_fd=rpipe)
    self.child.wait()
    self.child = None
예제 #8
0
  def run(self, player, id):
    if not player:
      return self.list_players()
    elif not doomrl.user_exists(player):
      return 'No such player: %s' % player
    elif not id:
      # FIXME: this currently shows the play time, not the ttyrec time.
      # This is because, with the new on-the-fly mortem parsing and elimination
      # of the score cache, determining the ttyrec time on the fly requires
      # reading all of the player's ttyrec files start to finish, which may be
      # hundreds of megabytes if they've played a lot of games, and we don't
      # want to do that every time someone types 'scores'.
      return doomrl.show_scores(self.replays(player))

    # Replay the named recording.
    replays = self.replays(player)
    if id == "latest":
      id = replays[-1]['n']
    else:
      try:
        id = int(id)
      except:
        return 'Invalid replay ID.'

    replay = doomrl.homepath('archive', '%d.ttyrec' % id, user=player)
    if not exists(replay):
      return 'No replay with that ID found for that player.'

    with TTYRec(replay) as ttyrec:
      ttyrec.ttyplay(osd_line=26)
예제 #9
0
    def run(self, player):
        if not player:
            # list active games
            print('   TIME PLAYER')
            for player in doomrl.all_users():
                if exists(doomrl.homepath('ttyrec', user=player)):
                    with TTYRec(path=doomrl.homepath('ttyrec',
                                                     user=player)) as ttyrec:
                        print(
                            "%7s %s" %
                            (str(timedelta(seconds=int(ttyrec.ttytime()[0]))),
                             player))
            return

        if not exists(doomrl.homepath('ttyrec', user=player)):
            return 'No game in progress under that name.'

        with TTYRec(doomrl.homepath('ttyrec', user=player)) as ttyrec:
            ttyrec.ttyplay(follow=True)
예제 #10
0
 def list_running_games(self):
   # List games in progress
   saves = os.listdir(doomrl.homepath('saves'))
   if not saves:
     print('You have no games in progress.')
   else:
     print('Games in progress:')
     for save in [s for s in saves if not s.endswith('.ttyrec')]:
       print('\t', save)
     print('Type "play <name>" to resume one.')
   return
예제 #11
0
 def list_running_games(self):
     # List games in progress
     saves = os.listdir(doomrl.homepath('saves'))
     if not saves:
         print('You have no games in progress.')
     else:
         print('Games in progress:')
         for save in [s for s in saves if not s.endswith('.ttyrec')]:
             print('\t', save)
         print('Type "play <name>" to resume one.')
     return
예제 #12
0
    def run(self, name):
        if not doomrl.user():
            return 'You must log in first.'

        saves = os.listdir(doomrl.homepath('saves'))
        if not name:
            # List games in progress
            if not saves:
                print('You have no games in progress.')
            else:
                print('Games in progress:')
                for save in [s for s in saves if not s.endswith('.ttyrec')]:
                    print('\t', save)
                print('Type "delete <name>" to delete one.')
            return

        if not name in saves:
            return 'No such game.'

        os.remove(doomrl.homepath('saves', name))
        os.remove(doomrl.homepath('saves', name + '.ttyrec'))
        print('In-progress game ' + name + ' deleted.')
예제 #13
0
  def run(self, name):
    if not doomrl.user():
      return 'You must log in first.'

    saves = os.listdir(doomrl.homepath('saves'))
    if not name:
      # List games in progress
      if not saves:
        print('You have no games in progress.')
      else:
        print('Games in progress:')
        for save in [s for s in saves if not s.endswith('.ttyrec')]:
          print('\t', save)
        print('Type "delete <name>" to delete one.')
      return

    if not name in saves:
      return 'No such game.'

    os.remove(doomrl.homepath('saves', name))
    os.remove(doomrl.homepath('saves', name + '.ttyrec'))
    print('In-progress game ' + name + ' deleted.')
예제 #14
0
  def run(self, name, password):
    # Check name validity
    if not doomrl.name_valid(name):
      return 'Invalid name.'

    # Check that password was specified
    if not password:
      return 'No password specified.'

    # Try to create user directory and die if we can't
    try:
      os.mkdir(doomrl.homepath(user=name))
    except OSError as e:
      return 'That name is unavailable.'

    # Fill in user directory
    log('Creating a new account: %s' % name)
    print('Creating user account.')
    try:
      doomrl.login(name)
      self.install(password)
    except Exception as e:
      doomrl.login()
      log('Error creating account %s: %s' % (name, e))
      print('Error creating user directory.')
      print('Report this to the server administrator.')
      if doomrl.debug():
        raise e
      try:
        shutil.rmtree(doomrl.homepath(user=name))
      except OSError as e:
        log('Error cleaning up account %s: %s' % (name, e))
        print('Error cleaning up the half-created user directory! This username is unavailable until the admin fixes things.')
      return 'Unable to create user.'

    return
예제 #15
0
  def run(self, name, password):
    # Check name validity
    if not doomrl.name_valid(name):
      return 'Invalid name.'

    # Check that password was specified
    if not password:
      return 'No password specified.'

    # Try to create user directory and die if we can't
    try:
      os.mkdir(doomrl.homepath(user=name))
    except OSError as e:
      return 'That name is unavailable.'

    # Fill in user directory
    log('Creating a new account: %s' % name)
    print('Creating user account.')
    try:
      doomrl.login(name)
      self.install(password)
    except Exception as e:
      doomrl.login()
      log('Error creating account %s: %s' % (name, e))
      print('Error creating user directory.')
      print('Report this to the server administrator.')
      if doomrl.debug():
        raise e
      try:
        shutil.rmtree(doomrl.homepath(user=name))
      except OSError as e:
        log('Error cleaning up account %s: %s' % (name, e))
        print('Error cleaning up the half-created user directory! This username is unavailable until the admin fixes things.')
      return 'Unable to create user.'

    return
예제 #16
0
  def run(self, name, password):
    if doomrl.user():
      return 'You are already logged in!'

    if not name or not password:
      return 'You must specify both a username and a password.'

    # Check password
    try:
      with open(doomrl.homepath('passwd', user=name)) as f:
        passwd = f.read()
      if passwd == password:
        log('%s successfully logged in.' % name)
        doomrl.login(name)
        return
    except IOError as e:
      pass

    log('Failed login attempt as %s' % name)
    doomrl.login()
    return 'Login failed.'
예제 #17
0
    def run(self, name):
        if not doomrl.user():
            return 'You must log in first.'

        if not name:
            return self.list_running_games()

        # We can be a bit looser about names here, but for simplicity's sake we
        # just reuse the name validation code we use for player names.
        if not doomrl.name_valid(name):
            return 'Invalid save name.'

        # Check that they aren't already playing *right now*.
        self.recfile = doomrl.homepath('ttyrec')
        if exists(self.recfile):
            return 'You are already playing in another window! Quit that game first.'

        (scores, mortem) = self.setup(name)
        try:
            self.run_doomrl()

        except OSError as e:
            # This almost certainly means that the user disconnected in mid-game.
            # Don't try to output any error messages, they'll just throw again because
            # our tty is gone.
            if self.child and (self.child.poll() is None):
                self.child.kill()
                self.child = None
            # Call _exit rather than using sys.exit so we don't try to run finalizers.
            os._exit(1)

        except Exception as e:
            import traceback
            log('Error running DoomRL: ' + traceback.format_exc())
            traceback.print_exc()
            sys.exit(1)
        finally:
            self.shutdown(name, scores, mortem)
예제 #18
0
  def run(self, name):
    if not doomrl.user():
      return 'You must log in first.'

    if not name:
      return self.list_running_games()

    # We can be a bit looser about names here, but for simplicity's sake we
    # just reuse the name validation code we use for player names.
    if not doomrl.name_valid(name):
      return 'Invalid save name.'

    # Check that they aren't already playing *right now*.
    self.recfile = doomrl.homepath('ttyrec')
    if exists(self.recfile):
      return 'You are already playing in another window! Quit that game first.'

    (scores,mortem) = self.setup(name)
    try:
      self.run_doomrl()

    except OSError as e:
      # This almost certainly means that the user disconnected in mid-game.
      # Don't try to output any error messages, they'll just throw again because
      # our tty is gone.
      if self.child and (self.child.poll() is None):
        self.child.kill()
        self.child = None
      # Call _exit rather than using sys.exit so we don't try to run finalizers.
      os._exit(1)

    except Exception as e:
      import traceback
      log('Error running DoomRL: ' + traceback.format_exc())
      traceback.print_exc()
      sys.exit(1)
    finally:
      self.shutdown(name, scores, mortem)
예제 #19
0
    def run(self, file, reset):
        if not doomrl.user():
            return 'You must be logged in.'

        if reset and reset != 'reset':
            return 'Invalid second argument. See "help config".'

        files = frozenset(['controls', 'colours', 'user'])

        if reset and file == "all":
            for file in files:
                reset(file)
            return

        if not file or file not in files:
            print('Try "config <controls|colours|user>" or "help config".')
            return

        if reset:
            reset(file)
            return

        # Use Nano in secure mode to edit the file.
        subprocess.call(['nano', '-R', doomrl.homepath('%s.lua' % file)])
예제 #20
0
    def run_doomrl(self):
        if exists(doomrl.datapath('ttysound', 'libSDL_mixer-1.2.so.0')):
            cmd = ['./doomrl']
            env = {
                **os.environ,
                "LD_LIBRARY_PATH": doomrl.datapath('ttysound'),
                "SDL_AUDIODRIVER": 'disk',
                'SDL_DISKAUDIOFILE': '/dev/null',
            }
        else:
            cmd = ['./doomrl', '-nosound']
            env = os.environ

        (rpipe, wpipe) = os.pipe()
        self.child = subprocess.Popen(cmd,
                                      stdout=wpipe,
                                      env=env,
                                      cwd=doomrl.homepath())
        os.close(wpipe)

        # DoomRL needs the terminal in raw mode, but since it's running inside ttyrec
        # it can't control that setting.
        tty.setraw(1)

        with TTYRec(self.recfile) as rec:
            # This will return when DoomRL closes the fd.
            # It will also automatically reset the TTY on leaving the 'with' block.
            rec.ttyrec(in_fd=rpipe)
        try:
            self.child.wait()
        except Exception as e:
            import traceback
            log('Error while DoomRL was running: ' + traceback.format_exc())
            self.child.kill()
            raise e
        self.child = None
예제 #21
0
  def shutdown(self, name, scores_before, mortem_before):
    # If DoomRL is still running, kill it.
    if self.child and (self.child.poll() is None):
      self.child.kill()
      self.child = None

    # If the game is still in progress, save the ttyrec file.
    if exists(doomrl.homepath('save')):
      os.rename(self.recfile, doomrl.homepath('saves', name + '.ttyrec'))
      os.rename(doomrl.homepath('save'), doomrl.homepath('saves', name))
      return

    # Otherwise, there *should* be a new high score entry and a new mortem.
    # Unless they simply didn't start a game in the first place.
    try:
      mortem = doomrl.raw_mortems()[-1]
    except IndexError:
      mortem = None

    if mortem == mortem_before:
      # No new postmortem created and no save file means no game played.
      try:
        os.remove(self.recfile)
      except FileNotFoundError:
        pass
      return

    scores_after = doomrl.raw_scores()
    n = len(scores_after)

    # Save the ttyrec and mortem files to the player archive directory.
    os.rename(self.recfile,
              doomrl.homepath('archive', '%d.ttyrec' % n))
    shutil.copy(doomrl.homepath('mortem', mortem),
                doomrl.homepath('archive', '%d.mortem' % n))

    doomrl.build_website('www')
예제 #22
0
    def shutdown(self, name, scores_before, mortem_before):
        # If DoomRL is still running, kill it.
        if self.child and (self.child.poll() is None):
            self.child.kill()
            self.child = None

        # If the game is still in progress, save the ttyrec file.
        if exists(doomrl.homepath('save')):
            os.rename(self.recfile, doomrl.homepath('saves', name + '.ttyrec'))
            os.rename(doomrl.homepath('save'), doomrl.homepath('saves', name))
            return

        # Otherwise, there *should* be a new high score entry and a new mortem.
        # Unless they simply didn't start a game in the first place.
        try:
            mortem = doomrl.raw_mortems()[-1]
        except IndexError:
            mortem = None

        if mortem == mortem_before:
            # No new postmortem created and no save file means no game played.
            try:
                os.remove(self.recfile)
            except FileNotFoundError:
                pass
            return

        scores_after = doomrl.raw_scores()
        n = len(scores_after)

        # Save the ttyrec and mortem files to the player archive directory.
        os.rename(self.recfile, doomrl.homepath('archive', '%d.ttyrec' % n))
        shutil.copy(doomrl.homepath('mortem', mortem),
                    doomrl.homepath('archive', '%d.mortem' % n))

        doomrl.build_website('www')
예제 #23
0
  def install(self, password):
    """Install a personal copy of DoomRL to the given path."""

    # Symlink in the DoomRL binary and WADs.
    for file in ['core.wad', 'doomrl', 'doomrl.wad']:
      os.symlink(doomrl.doompath(file), doomrl.homepath(file))

    # Copy in the user-editable config files, then symlink in all the rest.
    for file in ['colours.lua', 'controls.lua', 'user.lua']:
      shutil.copy(doomrl.datapath('config', file), doomrl.homepath(file))
    for file in os.listdir(doomrl.datapath('config')):
      if not exists(doomrl.homepath(file)):
        os.symlink(doomrl.datapath('config', file), doomrl.homepath(file))

    # Create directories for save files, postmortems, etc.
    for dir in ['backup', 'mortem', 'screenshot', 'saves', 'archive']:
      os.mkdir(doomrl.homepath(dir))

    # Write the password file.
    # No hashing or anything. This whole thing is fundamentally insecure, adding
    # a hash would just make it less obviously insecure without actually fixing
    # anything.
    with open(doomrl.homepath('passwd'), 'w') as passwd:
      passwd.write(password)
예제 #24
0
 def mortems(self, player):
     return [
         game for game in doomrl.games(player) if exists(
             doomrl.homepath(
                 'archive', '%d.mortem' % game['n'], user=player))
     ]
예제 #25
0
 def mortems(self, player):
   return [game for game in doomrl.games(player)
           if exists(doomrl.homepath('archive', '%d.mortem' % game['n'], user=player))]
예제 #26
0
    def setup(self, name):
        # Symlink in the DoomRL binary, WADs, and server configuration files.
        # We need to do this on each run because the doomrl or doomrl-server install
        # location may have changed since the last run, invalidating the old symlinks.
        for file in ['core.wad', 'doomrl', 'doomrl.wad']:
            if islink(doomrl.homepath(file)):
                os.unlink(doomrl.homepath(file))
            os.symlink(doomrl.doompath(file), doomrl.homepath(file))
        for file in os.listdir(doomrl.datapath('config')):
            if islink(doomrl.homepath(file)):
                os.unlink(doomrl.homepath(file))
            if not exists(doomrl.homepath(file)):
                os.symlink(doomrl.datapath('config', file),
                           doomrl.homepath(file))

        # If the player has a game in progress, restore their ttyrec and save.
        if exists(doomrl.homepath('saves', name + '.ttyrec')):
            os.rename(doomrl.homepath('saves', name + '.ttyrec'), self.recfile)
            print('Recording of current game restored.')
        if exists(doomrl.homepath('saves', name)):
            os.rename(doomrl.homepath('saves', name), doomrl.homepath('save'))

        # Get their pre-game high scores so we can check for new ones afterwards.
        scores_before = doomrl.raw_scores()
        try:
            mortem_before = doomrl.raw_mortems()[-1]
        except IndexError:
            mortem_before = None

        return (scores_before, mortem_before)
예제 #27
0
 def replays(self, player):
   return [game for game in doomrl.games(player)
           if exists(doomrl.homepath('archive', '%d.ttyrec' % game['n'], user=player))]
예제 #28
0
 def reset(self, file):
     print('Restoring %s to defaults.' % file)
     shutil.copy(doomrl.datapath('config', "%s.lua" % file),
                 doomrl.homepath("%.lua" % file))
예제 #29
0
 def reset(self, file):
   print('Restoring %s to defaults.' % file)
   shutil.copy(doomrl.datapath('config', "%s.lua" % file), doomrl.homepath("%.lua" % file))