added command to automatically patch tapes for the "EM random bug"
authorHolger Schemel <info@artsoft.org>
Thu, 10 Sep 2020 18:51:27 +0000 (20:51 +0200)
committerHolger Schemel <info@artsoft.org>
Thu, 10 Sep 2020 18:56:32 +0000 (20:56 +0200)
This adds a command line option that can be used to patch existing
tapes (by setting or unsetting property bits in the tape header).

The only patch mode currently available is "em_random_bug" that sets
the property bit for using the 64-bit random value workaround in the
EM game engine (see commit 1006b2db for details).

When using "help" for "MODE", all available patch modes will be shown.

src/init.c
src/main.c
src/main.h
src/tape.c
src/tape.h

index 2b43180b42f937d8bb27bf2fb4d4ceea0b6cb754..ffc856e3b3af75322c70130a5bffa4b90ac46043 100644 (file)
@@ -4900,6 +4900,7 @@ static void InitGlobal(void)
   }
 
   global.autoplay_leveldir = NULL;
+  global.patchtapes_leveldir = NULL;
   global.convert_leveldir = NULL;
   global.create_images_dir = NULL;
 
@@ -5080,6 +5081,65 @@ static void Execute_Command(char *command)
     if (global.autoplay_mode == AUTOPLAY_MODE_TEST)
       program.headless = TRUE;
   }
+  else if (strPrefix(command, "patch tapes "))
+  {
+    char *str_ptr = getStringCopy(&command[12]); // read command parameters
+
+    // skip leading whitespace
+    while (*str_ptr == ' ' || *str_ptr == '\t')
+      str_ptr++;
+
+    if (*str_ptr == '\0')
+      Error(ERR_EXIT, "cannot find MODE in command '%s'", command);
+
+    global.patchtapes_mode = str_ptr;          // store patch mode
+
+    // advance to next whitespace (or end of string)
+    while (*str_ptr != ' ' && *str_ptr != '\t' && *str_ptr != '\0')
+      str_ptr++;
+
+    while (*str_ptr != '\0')                   // continue parsing string
+    {
+      // cut leading whitespace from string, replace it by string terminator
+      while (*str_ptr == ' ' || *str_ptr == '\t')
+       *str_ptr++ = '\0';
+
+      if (*str_ptr == '\0')                    // end of string reached
+       break;
+
+      if (global.patchtapes_leveldir == NULL)  // read level set string
+      {
+       global.patchtapes_leveldir = str_ptr;
+       global.patchtapes_all = TRUE;           // default: patch all tapes
+
+       for (i = 0; i < MAX_TAPES_PER_SET; i++)
+         global.patchtapes_level[i] = FALSE;
+      }
+      else                                     // read level number string
+      {
+       int level_nr = atoi(str_ptr);           // get level_nr value
+
+       if (level_nr >= 0 && level_nr < MAX_TAPES_PER_SET)
+         global.patchtapes_level[level_nr] = TRUE;
+
+       global.patchtapes_all = FALSE;
+      }
+
+      // advance string pointer to the next whitespace (or end of string)
+      while (*str_ptr != ' ' && *str_ptr != '\t' && *str_ptr != '\0')
+       str_ptr++;
+    }
+
+    if (global.patchtapes_leveldir == NULL)
+    {
+      if (strEqual(global.patchtapes_mode, "help"))
+       global.patchtapes_leveldir = UNDEFINED_LEVELSET;
+      else
+       Error(ERR_EXIT, "cannot find LEVELDIR in command '%s'", command);
+    }
+
+    program.headless = TRUE;
+  }
   else if (strPrefix(command, "convert "))
   {
     char *str_copy = getStringCopy(strchr(command, ' ') + 1);
@@ -6167,6 +6227,11 @@ void OpenAll(void)
     AutoPlayTape();
     return;
   }
+  else if (global.patchtapes_leveldir)
+  {
+    PatchTapes();
+    return;
+  }
   else if (global.convert_leveldir)
   {
     ConvertLevels();
index 824c0b594c070f8e0349f42374d2f9065b79d187..a87d202947a98ffedac800315c61c1f8f23442a0 100644 (file)
@@ -7637,6 +7637,7 @@ static void print_usage(void)
        "  \"autoplay LEVELDIR [NR ...]\"     play level tapes for LEVELDIR\n"
        "  \"autoffwd LEVELDIR [NR ...]\"     ffwd level tapes for LEVELDIR\n"
        "  \"autowarp LEVELDIR [NR ...]\"     warp level tapes for LEVELDIR\n"
+       "  \"patch tapes MODE LEVELDIR [NR]\" patch level tapes for LEVELDIR\n"
        "  \"convert LEVELDIR [NR]\"          convert levels in LEVELDIR\n"
        "  \"create images DIRECTORY\"        write BMP images to DIRECTORY\n"
        "  \"create CE image DIRECTORY\"      write BMP image to DIRECTORY\n"
index c9f7a15a90301b69a49a26bb9f8368fd897384ec..92e4249219ab4967188c650488886d00600b659d 100644 (file)
@@ -3219,6 +3219,11 @@ struct GlobalInfo
   boolean autoplay_all;
   boolean autoplay_mode;
 
+  char *patchtapes_mode;
+  char *patchtapes_leveldir;
+  int patchtapes_level[MAX_TAPES_PER_SET];
+  boolean patchtapes_all;
+
   char *convert_leveldir;
   int convert_level_nr;
 
index 3f031f01018e117ef029ef04bea72d2c5457c8f3..1763b2969c3c18c123fdcf4032c9412bbc94536d 100644 (file)
@@ -1276,6 +1276,166 @@ void AutoPlayTape(void)
 }
 
 
+// ----------------------------------------------------------------------------
+// tape patch functions
+// ----------------------------------------------------------------------------
+
+static boolean PatchTape(struct TapeInfo *tape, char *mode)
+{
+  Print("[%d.%d.%d.%d]: ",
+       (tape->engine_version / 1000000) % 100,
+       (tape->engine_version / 10000  ) % 100,
+       (tape->engine_version / 100    ) % 100,
+       (tape->engine_version          ) % 100);
+
+  if (strEqual(mode, "info"))
+  {
+    Print("property bits == 0x%02x\n", tape->property_bits);
+
+    return FALSE;
+  }
+
+  byte property_bits = tape->property_bits;
+  byte property_bitmask = 0;
+  boolean set_property_bit = TRUE;
+
+  if (strSuffix(mode, ":0") ||
+      strSuffix(mode, ":off") ||
+      strSuffix(mode, ":clear"))
+    set_property_bit = FALSE;
+
+  if (strEqual(mode, "em_random_bug") || strPrefix(mode, "em_random_bug:"))
+  {
+    // this bug was introduced in version 3.3.1.0 and fixed in version 4.0.1.2
+    if (tape->engine_version <  VERSION_IDENT(3,3,1,0) ||
+       tape->engine_version >= VERSION_IDENT(4,0,1,2))
+    {
+      Print("This tape version cannot be patched against EM random bug!\n");
+
+      return FALSE;
+    }
+
+    property_bitmask = TAPE_PROPERTY_EM_RANDOM_BUG;
+  }
+  else
+  {
+    Print("Unknown patch mode '%s'!\n", mode);
+
+    return FALSE;
+  }
+
+  if (set_property_bit)
+    property_bits |= property_bitmask;
+  else
+    property_bits &= ~property_bitmask;
+
+  if (property_bits == tape->property_bits)
+  {
+    Print("Tape already patched for '%s'!\n", mode);
+
+    return FALSE;
+  }
+
+  Print("Patching for '%s' ... ", mode);
+
+  tape->property_bits = property_bits;
+
+  return TRUE;
+}
+
+void PatchTapes(void)
+{
+  static LevelDirTree *patchtapes_leveldir = NULL;
+  static int num_tapes_found = 0;
+  static int num_tapes_patched = 0;
+  char *mode = global.patchtapes_mode;
+  int i;
+
+  if (strEqual(mode, "help"))
+  {
+    PrintLine("=", 79);
+    Print("Supported patch modes:\n");
+    Print("- \"em_random_bug\"   - use 64-bit random value bug for EM engine\n");
+    PrintLine("-", 79);
+    Print("Supported modifiers:\n");
+    Print("- add \":0\", \":off\" or \":clear\" to patch mode to un-patch tape file\n");
+    PrintLine("=", 79);
+
+    CloseAllAndExit(0);
+  }
+
+  patchtapes_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+                                                 global.patchtapes_leveldir);
+
+  if (patchtapes_leveldir == NULL)
+    Error(ERR_EXIT, "no such level identifier: '%s'",
+         global.patchtapes_leveldir);
+
+  leveldir_current = patchtapes_leveldir;
+
+  if (patchtapes_leveldir->first_level < 0)
+    patchtapes_leveldir->first_level = 0;
+  if (patchtapes_leveldir->last_level >= MAX_TAPES_PER_SET)
+    patchtapes_leveldir->last_level = MAX_TAPES_PER_SET - 1;
+
+  PrintLine("=", 79);
+  Print("Patching level tapes for patch mode '%s'\n", mode);
+  PrintLine("-", 79);
+  Print("Level series identifier: '%s'\n", patchtapes_leveldir->identifier);
+  Print("Level series name:       '%s'\n", patchtapes_leveldir->name);
+  Print("Level series author:     '%s'\n", patchtapes_leveldir->author);
+  Print("Number of levels:        %d\n",   patchtapes_leveldir->levels);
+  PrintLine("=", 79);
+  Print("\n");
+
+  int first_level = patchtapes_leveldir->first_level;
+  int last_level = patchtapes_leveldir->last_level;
+
+  for (i = first_level; i <= last_level; i++)
+  {
+    if (!global.patchtapes_all && !global.patchtapes_level[i])
+      continue;
+
+    Print("Tape %03d: ", i);
+
+    TapeErase();
+    LoadTape(i);
+
+    if (tape.no_valid_file)
+    {
+      Print("(not found)\n");
+
+      continue;
+    }
+
+    num_tapes_found++;
+
+    if (PatchTape(&tape, mode))
+    {
+      char *filename = getTapeFilename(i);
+      char *filename_orig = getStringCat2(filename, ".orig");
+
+      if (!fileExists(filename_orig))
+       rename(filename, filename_orig);
+
+      SaveTapeToFilename(filename);
+
+      Print("patched tape saved.\n");
+
+      num_tapes_patched++;
+    }
+  }
+
+  Print("\n");
+  PrintLine("=", 79);
+  Print("Number of tapes found: %d\n", num_tapes_found);
+  Print("Number of tapes patched: %d\n", num_tapes_patched);
+  PrintLine("=", 79);
+
+  CloseAllAndExit(0);
+}
+
+
 // ---------- new tape button stuff -------------------------------------------
 
 static struct
index d5a19ac89c61c5eb890f21e0b1de6828bca28882..948227e53823088b232bdc6de22a5c4b20020065 100644 (file)
@@ -256,6 +256,7 @@ boolean InsertSolutionTape(void);
 boolean PlaySolutionTape(void);
 
 void AutoPlayTape(void);
+void PatchTapes(void);
 
 void CreateTapeButtons(void);
 void FreeTapeButtons(void);