rnd-20100309-2-src
authorHolger Schemel <info@artsoft.org>
Tue, 9 Mar 2010 22:35:25 +0000 (23:35 +0100)
committerHolger Schemel <info@artsoft.org>
Sat, 30 Aug 2014 08:58:43 +0000 (10:58 +0200)
* fixed potential crash bug caused by illegal array access in engine
  snapshot loading and saving code
* changed setting permissions of score files to be world-writable if
  the program is not installed and running setgid to allow the program
  to modify existing score files when run as a different user (which
  allows cheating, of course, as the score files are not protected
  against modification in this case)
* added (commented out) suggestions for RO_GAME_DIR and RW_GAME_DIR to
  the top level Makefile for Debian / Ubuntu installations
* added saving read-only levels from editor into personal level set
  (thanks to Bela Lubkin for the above four patches)

ChangeLog
Makefile
src/conftime.h
src/editor.c
src/files.c
src/game.c
src/libgame/setup.c
src/main.h

index 0d4ad4f787b10e111d000f3f6faae54f3a1ee2c4..96a007d5980d9b9e24784672db2f71aa4bafe30e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -5,6 +5,17 @@
          ".level"); to use this functionality, enter ":save-native-level" or
          ":snl" from the main menu with the native Supaplex level loaded and
          the appropriate tape loaded to the tape recorder
+       * fixed potential crash bug caused by illegal array access in engine
+         snapshot loading and saving code
+       * changed setting permissions of score files to be world-writable if
+         the program is not installed and running setgid to allow the program
+         to modify existing score files when run as a different user (which
+         allows cheating, of course, as the score files are not protected
+         against modification in this case)
+       * added (commented out) suggestions for RO_GAME_DIR and RW_GAME_DIR to
+         the top level Makefile for Debian / Ubuntu installations
+       * added saving read-only levels from editor into personal level set
+         (thanks to Bela Lubkin for the above four patches)
 
 2010-03-03
        * added updating of game values on the panel to Supaplex game engine
index c157ab6468e7e118c368e0f220559696df01f26c..0ab0dfc4821fd7601dffb136f0b4e5de72b10a3c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -22,10 +22,14 @@ X11_PATH = /usr/X11R6
 # directory for read-only game data (like graphics, sounds, levels)
 # default is '.' to be able to run program without installation
 # RO_GAME_DIR = /usr/games
+# use the following setting for Debian / Ubuntu installations:
+# RO_GAME_DIR = /usr/share/games/rocksndiamonds
 
 # directory for writable game data (like highscore files)
 # default is '.' to be able to run program without installation
 # RW_GAME_DIR = /var/games
+# use the following setting for Debian / Ubuntu installations:
+# RW_GAME_DIR = /var/games/rocksndiamonds
 
 # uncomment if system has no joystick include file
 # JOYSTICK = -DNO_JOYSTICK
index 303bcbd0dbaf02ebaba9cf015405597aac1a2bd7..0a5f28590397b68b19f9bbf5d06bc66f8d0d97c1 100644 (file)
@@ -1 +1 @@
-#define COMPILE_DATE_STRING "2010-03-09 14:59"
+#define COMPILE_DATE_STRING "2010-03-09 23:35"
index 1395f21c634d1d33270f9d5f9e3779eb082a00aa..bbc863cae7e21318aad9f1ad2137a63187cd1c8b 100644 (file)
@@ -6630,8 +6630,12 @@ static boolean LevelChanged()
   boolean field_changed = FALSE;
   int x, y;
 
+#if 1
+  /* changed read-only levels can now be saved in personal level set */
+#else
   if (leveldir_current->readonly)
     return FALSE;
+#endif
 
   for (y = 0; y < lev_fieldy; y++) 
     for (x = 0; x < lev_fieldx; x++)
@@ -6641,21 +6645,151 @@ static boolean LevelChanged()
   return (level.changed || field_changed);
 }
 
-static boolean LevelContainsPlayer()
+static boolean PrepareSavingIntoPersonalLevelSet()
 {
-  boolean player_found = FALSE;
-  int x, y;
+  static LevelDirTree *last_copied_leveldir = NULL;
+  static LevelDirTree *last_written_leveldir = NULL;
+  static int last_copied_level_nr = -1;
+  static int last_written_level_nr = -1;
+  LevelDirTree *leveldir_former = leveldir_current;
+  int level_nr_former = level_nr;
+  int new_level_nr;
+
+  // remember last mod/save so that for current session, we write
+  // back to the same personal copy, asking only about overwrite.
+  if (leveldir_current == last_copied_leveldir &&
+      level_nr == last_copied_level_nr)
+  {
+    // "cd" to personal level set dir (as used when writing last copy)
+    leveldir_current = last_written_leveldir;
+    level_nr = last_written_level_nr;
 
-  return TRUE;         /* !!! CURRENTLY DEACTIVATED !!! */
+    return TRUE;
+  }
+
+  if (!Request("This level is read only ! "
+              "Save into personal level set ?", REQ_ASK))
+    return FALSE;
 
-  for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+  // "cd" to personal level set dir (for writing copy the first time)
+  leveldir_current =
+    getTreeInfoFromIdentifier(leveldir_first, getLoginName());
+
+  // find unused level number
+  for (new_level_nr = leveldir_current->first_level; ; new_level_nr++)
   {
-    if (Feld[x][y] == EL_PLAYER_1 ||
-       Feld[x][y] == EL_SP_MURPHY) 
-      player_found = TRUE;
+    static char *level_filename = NULL;
+
+    setString(&level_filename, getDefaultLevelFilename(new_level_nr));
+
+    if (!fileExists(level_filename))
+      break;
   }
 
-  return player_found;
+  last_copied_leveldir = leveldir_former;
+  last_copied_level_nr = level_nr_former;
+
+  last_written_leveldir = leveldir_current;
+  last_written_level_nr = level_nr = new_level_nr;
+
+  return TRUE;
+}
+
+static void ModifyLevelInfoForSavingIntoPersonalLevelSet(char *former_name)
+{
+  static char *filename_levelinfo = NULL, *mod_name = NULL;
+  FILE *file;
+
+  // annotate this copy-and-mod in personal levelinfo.conf
+  setString(&filename_levelinfo,
+           getPath2(getCurrentLevelDir(), LEVELINFO_FILENAME));
+
+  if ((file = fopen(filename_levelinfo, MODE_APPEND)))
+  {
+    fprintf(file, "\n");
+    fprintf(file, "# level %d was modified from:\n", level_nr);
+    fprintf(file, "# - previous level set name:    %s\n",
+           former_name);
+    fprintf(file, "# - level within previous set:  %d \"%s\"\n",
+           level.file_info.nr, level.name);
+    fprintf(file, "# - previous author:            %s\n",
+           level.author);
+    fprintf(file, "# - previous save date:         ");
+
+    if (level.creation_date.src == DATE_SRC_LEVELFILE)
+    {
+      fprintf(file, "%04d-%02d-%02d\n",
+             level.creation_date.year,
+             level.creation_date.month,
+             level.creation_date.day);
+    }
+    else
+    {
+      fprintf(file, "not recorded\n");
+    }
+
+    fclose(file);
+  }
+
+  if (level_nr > leveldir_current->last_level)
+  {
+    static char *temp_levelinfo = NULL;
+    FILE *temp_file = NULL;
+    char line[MAX_LINE_LEN];
+
+    setString(&temp_levelinfo,
+             getPath2(getCurrentLevelDir(),
+                      getStringCat2(LEVELINFO_FILENAME, ".new")));
+
+    if ((file = fopen(filename_levelinfo, MODE_READ)) &&
+       (temp_file = fopen(temp_levelinfo, MODE_WRITE)))
+    {
+      while (fgets(line, MAX_LINE_LEN, file))
+      {
+       if (!strPrefix(line, "levels:"))
+         fputs(line, temp_file);
+       else
+         fprintf(temp_file, "%-32s%d\n", "levels:", level_nr + 9);
+      }
+    }
+
+    if (temp_file)
+      fclose(temp_file);
+
+    if (file)
+      fclose(file);
+
+    // needs error handling; also, ok on dos/win?
+    unlink(filename_levelinfo);
+    rename(temp_levelinfo, filename_levelinfo);
+  }
+
+  // else: allow the save even if annotation failed
+
+  // now... spray graffiti on the old level vital statistics
+  // user can change these; just trying to set a good baseline
+
+  // don't truncate names for fear of making offensive or silly:
+  // long-named original author only recorded in levelinfo.conf.
+  // try to fit "Joe after Bob", "Joe (ed.)", then just "Joe"
+  if (!strEqual(level.author, leveldir_current->author))
+  {
+    setString(&mod_name, getStringCat3(leveldir_current->author,
+                                      " after ", level.author));
+
+    if (strlen(mod_name) > MAX_LEVEL_AUTHOR_LEN)
+      setString(&mod_name,
+               getStringCat2(leveldir_current->author, " (ed.)"));
+
+    if (strlen(mod_name) > MAX_LEVEL_AUTHOR_LEN)
+      setString(&mod_name, leveldir_current->author);
+
+    strncpy(level.author, mod_name, MAX_LEVEL_AUTHOR_LEN);
+
+    // less worried about truncation here
+    setString(&mod_name, getStringCat2("Mod: ", level.name));
+    strncpy(level.name, mod_name, MAX_LEVEL_NAME_LEN);
+  }
 }
 
 static void CopyPlayfield(short src[MAX_LEV_FIELDX][MAX_LEV_FIELDY],
@@ -11237,60 +11371,76 @@ static void HandleControlButtons(struct GadgetInfo *gi)
       break;
 
     case GADGET_ID_SAVE:
-      if (leveldir_current->readonly)
-      {
-       Request("This level is read only !", REQ_CONFIRM);
+    {
+      /* saving read-only levels into personal level set modifies global vars
+        "leveldir_current" and "level_nr"; restore them after saving level */
+      LevelDirTree *leveldir_former = leveldir_current;
+      int level_nr_former = level_nr;
+      char *level_filename;
+      boolean new_level;
+
+      if (leveldir_current->readonly &&
+         !PrepareSavingIntoPersonalLevelSet())
        break;
-      }
 
-      if (!LevelContainsPlayer())
-       Request("No Level without Gregor Mc Duffin please !", REQ_CONFIRM);
-      else
+      level_filename = getDefaultLevelFilename(level_nr);
+      new_level = !fileExists(level_filename);
+
+      if (new_level ||
+         Request("Save this level and kill the old ?", REQ_ASK))
       {
-       char *level_filename = getDefaultLevelFilename(level_nr);
-       boolean new_level = !fileExists(level_filename);
+       if (leveldir_former->readonly)
+         ModifyLevelInfoForSavingIntoPersonalLevelSet(leveldir_former->name);
 
-       if (new_level ||
-           Request("Save this level and kill the old ?", REQ_ASK))
-       {
-         CopyPlayfield(Feld, level.field);
+       CopyPlayfield(Feld, level.field);
+       SaveLevel(level_nr);
 
-         SaveLevel(level_nr);
-       }
+       level.changed = FALSE;
 
        if (new_level)
-         Request("Level saved !", REQ_CONFIRM);
+       {
+         char level_saved_msg[64];
 
-       level.changed = FALSE;
+         if (leveldir_former->readonly)
+           sprintf(level_saved_msg,
+                   "Level saved as level %d into personal level set !",
+                   level_nr);
+         else
+           strcpy(level_saved_msg, "Level saved !");
+
+         Request(level_saved_msg, REQ_CONFIRM);
+       }
       }
+
+      /* "cd" back to copied-from levelset (in case of saved read-only level) */
+      leveldir_current = leveldir_former;
+      level_nr = level_nr_former;
+
       break;
+    }
 
     case GADGET_ID_TEST:
-      if (!LevelContainsPlayer())
-       Request("No Level without Gregor Mc Duffin please !", REQ_CONFIRM);
-      else
-      {
-       if (LevelChanged())
-         level.game_version = GAME_VERSION_ACTUAL;
+      if (LevelChanged())
+       level.game_version = GAME_VERSION_ACTUAL;
 
-       CopyPlayfield(level.field, FieldBackup);
-       CopyPlayfield(Feld, level.field);
+      CopyPlayfield(level.field, FieldBackup);
+      CopyPlayfield(Feld, level.field);
 
-       CopyNativeLevel_RND_to_Native(&level);
+      CopyNativeLevel_RND_to_Native(&level);
 
-       UnmapLevelEditorGadgets();
-       UndrawSpecialEditorDoor();
+      UnmapLevelEditorGadgets();
+      UndrawSpecialEditorDoor();
 
-       CloseDoor(DOOR_CLOSE_ALL);
+      CloseDoor(DOOR_CLOSE_ALL);
 
-       BackToFront();          /* force redraw of undrawn special door */
+      BackToFront();           /* force redraw of undrawn special door */
 
-       DrawCompleteVideoDisplay();
+      DrawCompleteVideoDisplay();
 
-       level_editor_test_game = TRUE;
+      level_editor_test_game = TRUE;
+
+      StartGameActions(FALSE, setup.autorecord, level.random_seed);
 
-       StartGameActions(FALSE, setup.autorecord, level.random_seed);
-      }
       break;
 
     case GADGET_ID_EXIT:
index 6304da1f670e92fce336c84b8cde533349324820..124b1db04b05c7b8fce2925962392718e6f9f7bb 100644 (file)
@@ -1346,6 +1346,8 @@ static struct DateInfo getCurrentDate()
   date.month = now->tm_mon  + 1;
   date.day   = now->tm_mday;
 
+  date.src   = DATE_SRC_CLOCK;
+
   return date;
 }
 
@@ -2225,6 +2227,8 @@ static int LoadLevel_DATE(FILE *file, int chunk_size, struct LevelInfo *level)
   level->creation_date.month = getFile8Bit(file);
   level->creation_date.day   = getFile8Bit(file);
 
+  level->creation_date.src   = DATE_SRC_LEVELFILE;
+
   return chunk_size;
 }
 
index 46f050d2c3c18dd37c7ab8ae7fcdc40b3de2ad7a..d19831759e264f2419d09ad36316986eefa617ae 100644 (file)
@@ -16440,8 +16440,8 @@ struct EngineSnapshotInfo
   int choice_pos[NUM_GROUP_ELEMENTS];
 
   /* runtime values for belt position animations */
-  int belt_graphic[4 * NUM_BELT_PARTS];
-  int belt_anim_mode[4 * NUM_BELT_PARTS];
+  int belt_graphic[4][NUM_BELT_PARTS];
+  int belt_anim_mode[4][NUM_BELT_PARTS];
 };
 
 static struct EngineSnapshotInfo engine_snapshot_rnd;
@@ -16481,8 +16481,8 @@ static void SaveEngineSnapshotValues_RND()
       int graphic = el2img(element);
       int anim_mode = graphic_info[graphic].anim_mode;
 
-      engine_snapshot_rnd.belt_graphic[i * 4 + j] = graphic;
-      engine_snapshot_rnd.belt_anim_mode[i * 4 + j] = anim_mode;
+      engine_snapshot_rnd.belt_graphic[i][j] = graphic;
+      engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
     }
   }
 }
@@ -16510,8 +16510,8 @@ static void LoadEngineSnapshotValues_RND()
   {
     for (j = 0; j < NUM_BELT_PARTS; j++)
     {
-      int graphic = engine_snapshot_rnd.belt_graphic[i * 4 + j];
-      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i * 4 + j];
+      int graphic = engine_snapshot_rnd.belt_graphic[i][j];
+      int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
 
       graphic_info[graphic].anim_mode = anim_mode;
     }
index dbe124df27cea2ab7a0df8c39b025d81e5f05d41..62ba9fc548715795de5b120b1389bf06057b5dcd 100644 (file)
@@ -1410,21 +1410,41 @@ static int posix_mkdir(const char *pathname, mode_t mode)
 #endif
 }
 
+static boolean posix_process_running_setgid()
+{
+#if defined(PLATFORM_UNIX)
+  return (getgid() != getegid());
+#else
+  return FALSE;
+#endif
+}
+
 void createDirectory(char *dir, char *text, int permission_class)
 {
   /* leave "other" permissions in umask untouched, but ensure group parts
      of USERDATA_DIR_MODE are not masked */
   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
                     DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
-  mode_t normal_umask = posix_umask(0);
+  mode_t last_umask = posix_umask(0);
   mode_t group_umask = ~(dir_mode & S_IRWXG);
-  posix_umask(normal_umask & group_umask);
+  int running_setgid = posix_process_running_setgid();
+
+  /* if we're setgid, protect files against "other" */
+  /* else keep umask(0) to make the dir world-writable */
+
+  if (running_setgid)
+    posix_umask(last_umask & group_umask);
+  else
+    dir_mode |= MODE_W_ALL;
 
   if (!fileExists(dir))
     if (posix_mkdir(dir, dir_mode) != 0)
       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
 
-  posix_umask(normal_umask);           /* reset normal umask */
+  if (permission_class == PERMS_PUBLIC && !running_setgid)
+    chmod(dir, dir_mode);
+
+  posix_umask(last_umask);             /* restore previous umask */
 }
 
 void InitUserDataDirectory()
@@ -1434,8 +1454,14 @@ void InitUserDataDirectory()
 
 void SetFilePermissions(char *filename, int permission_class)
 {
-  chmod(filename, (permission_class == PERMS_PRIVATE ?
-                  FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
+  int running_setgid = posix_process_running_setgid();
+  int perms = (permission_class == PERMS_PRIVATE ?
+              FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
+
+  if (permission_class == PERMS_PUBLIC && !running_setgid)
+    perms |= MODE_W_ALL;
+
+  chmod(filename, perms);
 }
 
 char *getCookie(char *file_type)
index 19deaee03fb1439d7bb71852c44dc3b186bca1be..9376b3ae4095ff270f3e5d5346ae3f30aabb5a88 100644 (file)
@@ -2287,6 +2287,12 @@ struct DateInfo
   int year;
   int month;
   int day;
+
+  enum
+  {
+    DATE_SRC_CLOCK,
+    DATE_SRC_LEVELFILE
+  } src;
 };
 
 struct LevelInfo