launcher: added ability to specify a different data installation directory. Changes since the first version: added re-building of the game mod lists and updating of several game options; added disabling of the custom dir option during typing. Changes since the second version: cleaned-up game mod list building (the first entry in the lookup table is always available); un-checked the portals toggle button only if the portals option is set. diff -urNp uhexen2-20070523/launcher/config_file.c uhexen2-20070531/launcher/config_file.c --- uhexen2-20070523/launcher/config_file.c 2007-04-28 18:31:08.000000000 +0300 +++ uhexen2-20070531/launcher/config_file.c 2007-05-31 19:27:03.000000000 +0300 @@ -29,6 +29,8 @@ #include "config_file.h" // Default values for the options +char game_basedir[MAX_OSPATH]; +int basedir_nonstd = 0; int destiny = DEST_H2; int opengl_support = 1; int fullscreen = 1; @@ -87,13 +89,14 @@ int write_config_file (void) { fprintf(stderr, " Error: couldn't open config file for writing\n"); return 1; - } else { fprintf(cfg_file, "# Hexen II Launcher Options file\n\n"); fprintf(cfg_file, "# This file has been automatically generated\n\n"); + fprintf(cfg_file, "game_basedir=\"%s\"\n",game_basedir); + fprintf(cfg_file, "basedir_nonstd=%d\n",basedir_nonstd); fprintf(cfg_file, "destiny=%d\n",destiny); #ifndef DEMOBUILD fprintf(cfg_file, "h2game=%d\n",h2game); @@ -132,6 +135,70 @@ int write_config_file (void) return 0; } +int cfg_read_basedir (void) +{ + FILE *cfg_file; + char buff[1024], *tmp; + + game_basedir[0] = '\0'; + cfg_file = open_config_file("r"); + if (cfg_file == NULL) + { + printf("Creating default configuration file...\n"); + return write_config_file(); + } + else + { + int cnt = 0; + + do { + memset(buff, 0, sizeof(buff)); + fgets(buff, sizeof(buff), cfg_file); + if (!feof(cfg_file)) + { + if (buff[0] == '#') + continue; + // remove end-of-line characters + tmp = buff; + while (*tmp) + { + if (*tmp == '\r' || *tmp == '\n') + *tmp = '\0'; + tmp++; + } + // parse: whitespace isn't tolerated. + if (strstr(buff, "game_basedir=") == buff) + { + size_t len; + tmp = buff+13; + len = strlen(tmp); + // first and last chars must be quotes + if (tmp[0] != '\"' || tmp[len-1] != '\"' || len-2 >= sizeof(game_basedir)) + continue; + memset (game_basedir, 0, sizeof(game_basedir)); + memcpy (game_basedir, tmp+1, len-2); + ++cnt; + } + else if (strstr(buff, "basedir_nonstd=") == buff) + { + basedir_nonstd = atoi(buff + 15); + if (basedir_nonstd != 0 && basedir_nonstd != 1) + basedir_nonstd = 0; + ++cnt; + } + + if (cnt >= 2) + break; + } + + } while (!feof(cfg_file)); + + fclose (cfg_file); + } + + return 0; +} + int read_config_file (void) { FILE *cfg_file; @@ -141,8 +208,7 @@ int read_config_file (void) if (cfg_file == NULL) { printf("Creating default configuration file...\n"); - write_config_file(); - return 0; + return write_config_file(); } else { diff -urNp uhexen2-20070523/launcher/config_file.h uhexen2-20070531/launcher/config_file.h --- uhexen2-20070523/launcher/config_file.h 2007-04-15 23:40:38.000000000 +0300 +++ uhexen2-20070531/launcher/config_file.h 2007-05-31 19:27:03.000000000 +0300 @@ -28,6 +28,8 @@ #define LAUNCHER_CONFIG_FILE "launcher_options" +extern char game_basedir[MAX_OSPATH]; +extern int basedir_nonstd; extern int destiny; extern int opengl_support; extern int fullscreen; @@ -63,6 +65,7 @@ extern int hwgame; int write_config_file (void); int read_config_file (void); +int cfg_read_basedir (void); #endif // CONFIG_FILE_H diff -urNp uhexen2-20070523/launcher/games.c uhexen2-20070531/launcher/games.c --- uhexen2-20070523/launcher/games.c 2007-04-28 18:49:16.000000000 +0300 +++ uhexen2-20070531/launcher/games.c 2007-05-31 19:42:13.000000000 +0300 @@ -27,6 +27,7 @@ #include "games.h" #include "crc.h" #include "pakfile.h" +#include "config_file.h" #if !defined(LITTLE_ENDIAN) || !defined(BIG_ENDIAN) #undef LITTLE_ENDIAN @@ -65,6 +66,7 @@ static int LongSwap (int l) } unsigned int gameflags; +static char *scan_dir; typedef struct { @@ -189,7 +191,7 @@ finish: } #if !defined(DEMOBUILD) -h2game_t h2game_names[] = +h2game_t h2game_names[] = /* first entry is always available */ { { NULL , "( None )" , NULL, 0, 1 }, { "hcbots" , "BotMatch: HC bots", "progs.dat", 1, 0 }, @@ -200,7 +202,7 @@ h2game_t h2game_names[] = const int MAX_H2GAMES = sizeof(h2game_names) / sizeof(h2game_names[0]); -hwgame_t hwgame_names[] = +hwgame_t hwgame_names[] = /* first entry is always available */ { { NULL , "Plain DeathMatch", NULL, 1 }, { "hexarena", "HexArena" , "sound/ha/fight.wav", 0 }, @@ -212,12 +214,14 @@ hwgame_t hwgame_names[] = const int MAX_HWGAMES = sizeof(hwgame_names) / sizeof(hwgame_names[0]); -static size_t string_size = 0; +static size_t string_size; static void FindMaxStringSize (void) { size_t i, len; + string_size = 0; + for (i = 1; i < MAX_H2GAMES; i++) { len = strlen(h2game_names[i].dirname) + strlen(hwgame_names[i].checkfile); @@ -235,7 +239,7 @@ static void FindMaxStringSize (void) string_size = len; } - string_size += 2; // 1 for "/" + 1 for null termination + string_size = string_size + strlen(scan_dir) + 3; // 2 for two "/" + 1 for null termination } static void scan_h2_mods (void) @@ -243,14 +247,11 @@ static void scan_h2_mods (void) int i; char *path; - if (!string_size) - FindMaxStringSize (); - printf ("Scanning for known hexen2 mods\n"); path = (char *)malloc(string_size); for (i = 1; i < MAX_H2GAMES; i++) { - sprintf (path, "%s/%s", h2game_names[i].dirname, h2game_names[i].checkfile); + sprintf (path, "%s/%s/%s", scan_dir, h2game_names[i].dirname, h2game_names[i].checkfile); if (access(path, R_OK) == 0) h2game_names[i].available = 1; } @@ -262,18 +263,15 @@ static void scan_hw_mods (void) int i, j; char *path; - if (!string_size) - FindMaxStringSize (); - printf ("Scanning for known hexenworld mods\n"); path = (char *)malloc(string_size); for (i = 1; i < MAX_HWGAMES; i++) { - sprintf (path, "%s/hwprogs.dat", hwgame_names[i].dirname); + sprintf (path, "%s/%s/hwprogs.dat", scan_dir, hwgame_names[i].dirname); j = access(path, R_OK); if (j == 0) { - sprintf (path, "%s/%s", hwgame_names[i].dirname, hwgame_names[i].checkfile); + sprintf (path, "%s/%s/%s", scan_dir, hwgame_names[i].dirname, hwgame_names[i].checkfile); j = access(path, R_OK); } if (j == 0) @@ -315,10 +313,15 @@ void scan_game_installation (void) if (endien == 0) printf ("Warning: Unknown byte order!\n"); + if (basedir_nonstd && game_basedir[0]) + scan_dir = game_basedir; + else + scan_dir = basedir; + printf ("Scanning base hexen2 installation\n"); for (i = 0; i < MAX_PAKDATA-1; i++) { - snprintf (pakfile, sizeof(pakfile), "%s/pak%d.pak", pakdata[i].dirname, i); + snprintf (pakfile, sizeof(pakfile), "%s/%s/pak%d.pak", scan_dir, pakdata[i].dirname, i); scan_pak_files (pakfile, i); } @@ -357,6 +360,7 @@ void scan_game_installation (void) } #if !defined(DEMOBUILD) + FindMaxStringSize (); scan_h2_mods (); scan_hw_mods (); #endif /* DEMOBUILD */ diff -urNp uhexen2-20070523/launcher/interface.c uhexen2-20070531/launcher/interface.c --- uhexen2-20070523/launcher/interface.c 2007-04-28 18:31:08.000000000 +0300 +++ uhexen2-20070531/launcher/interface.c 2007-05-31 19:41:31.000000000 +0300 @@ -58,6 +58,10 @@ int thread_alive; // Private data: static GtkTooltips *tooltips; +static GtkWidget *H2G_Entry; // Hexen2 games listing +#ifndef DEMOBUILD +static GtkWidget *HWG_Entry; // Hexenworld games listing +#endif /* DEMOBUILD */ static options_widget_t Options; static MainWindow_t main_win; @@ -218,6 +222,7 @@ static void report_status (GtkObject *Un Log_printf ("Installation Summary:\n\n"); Log_printf ("Base directory: %s\n", basedir); + Log_printf ("Data directory: %s\n", (basedir_nonstd && game_basedir[0]) ? game_basedir : basedir); Log_printf ("PAK file health: %s", (gameflags & GAME_INSTBAD) ? "BAD. Reason(s):\n" : "OK "); if (gameflags & GAME_INSTBAD) { @@ -575,6 +580,81 @@ static void on_MORE (GtkButton *button, gtk_widget_hide (BOOK1); } +static void basedir_Change (GtkButton *unused, gpointer user_data) +{ +#if !defined(DEMOBUILD) + int i; + GList *TmpList = NULL; +#endif /* ! DEMOBUILD */ + + if (lock) + return; + + lock = 1; + basedir_nonstd ^= 1; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(patch_win.bBASEDIR), basedir_nonstd); + scan_game_installation(); + UpdateStats (); + report_status (NULL, &patch_win); + +// activate the extra game options, if necessary + gtk_widget_set_sensitive (WGT_H2WORLD, (gameflags & GAME_HEXENWORLD) ? TRUE : FALSE); +// rebuild the game mod lists +#if !defined(DEMOBUILD) + if (mp_support) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(WGT_PORTALS), FALSE); + h2game = hwgame = mp_support = 0; + for (i = 0; i < MAX_H2GAMES; i++) + { + if (h2game_names[i].available) + TmpList = g_list_append (TmpList, h2game_names[i].name); + } + gtk_combo_set_popdown_strings (GTK_COMBO(WGT_H2GAME), TmpList); + g_list_free (TmpList); + TmpList = NULL; + for (i = 0; i < MAX_HWGAMES; i++) + { + if (hwgame_names[i].available) + TmpList = g_list_append (TmpList, hwgame_names[i].name); + } + gtk_combo_set_popdown_strings (GTK_COMBO(WGT_HWGAME), TmpList); + g_list_free (TmpList); + gtk_entry_set_text (GTK_ENTRY(H2G_Entry), h2game_names[0].name); + gtk_entry_set_text (GTK_ENTRY(HWG_Entry), hwgame_names[0].name); + if (gameflags & (GAME_REGISTERED|GAME_REGISTERED_OLD)) + { + gtk_widget_set_sensitive (WGT_H2GAME, TRUE); + gtk_widget_set_sensitive (WGT_HWGAME, TRUE); + if (gameflags & GAME_PORTALS && destiny == DEST_H2) + gtk_widget_set_sensitive (WGT_PORTALS, TRUE); + } + else + { + gtk_widget_set_sensitive (WGT_H2GAME, FALSE); + gtk_widget_set_sensitive (WGT_HWGAME, FALSE); + gtk_widget_set_sensitive (WGT_PORTALS, FALSE); + } +#endif /* ! DEMOBUILD */ + + lock = 0; +} + +static void basedir_ChangePath (GtkEditable *editable, gpointer user_data) +{ + size_t len; + gchar *tmp = gtk_editable_get_chars (editable, 0, -1); + len = strlen(tmp); + if (len > sizeof(game_basedir)-1) + len = sizeof(game_basedir)-1; + if (len) + memcpy (game_basedir, tmp, len); + game_basedir[len] = 0; + g_free (tmp); + + if (basedir_nonstd) /* FIXME: any better way? */ + basedir_Change (NULL, NULL); +} + /********************************************************************* WINDOW CREATING *********************************************************************/ @@ -599,7 +679,7 @@ static void create_window2 (GtkWidget *u gtk_window_set_title (GTK_WINDOW(PATCH_WINDOW), "Hexen II PAK patch"); gtk_window_set_resizable (GTK_WINDOW(PATCH_WINDOW), FALSE); gtk_window_set_modal (GTK_WINDOW(PATCH_WINDOW), TRUE); - gtk_widget_set_size_request(PATCH_WINDOW, 360, 240); + gtk_widget_set_size_request(PATCH_WINDOW, 360, 272); PATCH_TAB = gtk_fixed_new (); gtk_widget_ref (PATCH_TAB); @@ -613,11 +693,30 @@ static void create_window2 (GtkWidget *u gtk_fixed_put (GTK_FIXED(PATCH_TAB), Txt1, 14, 12); gtk_label_set_justify (GTK_LABEL(Txt1), GTK_JUSTIFY_LEFT); +// custom basedir entry: + patch_win.bBASEDIR = gtk_check_button_new_with_label (_("Data install path:")); + gtk_widget_ref (patch_win.bBASEDIR); + gtk_widget_show (patch_win.bBASEDIR); + gtk_fixed_put (GTK_FIXED(PATCH_TAB), patch_win.bBASEDIR, 14, 186); + gtk_widget_set_size_request (patch_win.bBASEDIR, 128, 24); + gtk_widget_set_sensitive (patch_win.bBASEDIR, TRUE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(patch_win.bBASEDIR), basedir_nonstd); + GTK_WIDGET_UNSET_FLAGS (patch_win.bBASEDIR, GTK_CAN_FOCUS); + gtk_tooltips_set_tip (tooltips, patch_win.bBASEDIR, _("Mark this in order to use a different game installation directory"), NULL); + + patch_win.dir_Entry = gtk_entry_new(); + gtk_widget_show (patch_win.dir_Entry); + gtk_fixed_put (GTK_FIXED(PATCH_TAB), patch_win.dir_Entry, 148, 186); + gtk_widget_set_size_request (patch_win.dir_Entry, 190, 24); + gtk_entry_set_max_length (GTK_ENTRY(patch_win.dir_Entry), sizeof(game_basedir)-1); + gtk_entry_set_text (GTK_ENTRY(patch_win.dir_Entry), game_basedir); + gtk_widget_ref (patch_win.dir_Entry); + // Apply Patch button patch_win.bAPPLY = gtk_button_new_with_label (_("Apply Pak Patch")); gtk_widget_ref (patch_win.bAPPLY); gtk_widget_show (patch_win.bAPPLY); - gtk_fixed_put (GTK_FIXED(PATCH_TAB), patch_win.bAPPLY, 14, 186); + gtk_fixed_put (GTK_FIXED(PATCH_TAB), patch_win.bAPPLY, 14, 218); gtk_widget_set_size_request (patch_win.bAPPLY, 112, 24); #if !defined(DEMOBUILD) gtk_tooltips_set_tip (tooltips, patch_win.bAPPLY, _("Apply the v1.11 pakfiles patch by Raven Software."), NULL); @@ -627,7 +726,7 @@ static void create_window2 (GtkWidget *u patch_win.bREPORT = gtk_button_new_with_label (_("Make Report")); gtk_widget_ref (patch_win.bREPORT); gtk_widget_show (patch_win.bREPORT); - gtk_fixed_put (GTK_FIXED(PATCH_TAB), patch_win.bREPORT, 132, 186); + gtk_fixed_put (GTK_FIXED(PATCH_TAB), patch_win.bREPORT, 132, 218); gtk_widget_set_size_request (patch_win.bREPORT, 112, 24); // Holder window for the textview @@ -669,14 +768,14 @@ static void create_window2 (GtkWidget *u patch_win.bCLOSE = gtk_button_new_with_label (_("Close")); gtk_widget_ref (patch_win.bCLOSE); gtk_widget_show (patch_win.bCLOSE); - gtk_fixed_put (GTK_FIXED(PATCH_TAB), patch_win.bCLOSE, 250, 186); + gtk_fixed_put (GTK_FIXED(PATCH_TAB), patch_win.bCLOSE, 250, 218); gtk_widget_set_size_request (patch_win.bCLOSE, 88, 24); // Statusbar PATCH_STATBAR = gtk_statusbar_new (); gtk_widget_ref (PATCH_STATBAR); gtk_widget_show (PATCH_STATBAR); - gtk_fixed_put (GTK_FIXED(PATCH_TAB), PATCH_STATBAR, 0, 214); + gtk_fixed_put (GTK_FIXED(PATCH_TAB), PATCH_STATBAR, 0, 246); gtk_widget_set_size_request (PATCH_STATBAR, 354, 24); gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR(PATCH_STATBAR), FALSE); gtk_container_set_border_width (GTK_CONTAINER(PATCH_STATBAR), 2); @@ -687,6 +786,8 @@ static void create_window2 (GtkWidget *u gtk_object_set_data_full (GTK_OBJECT(PATCH_WINDOW), "Txt1", Txt1, GTK_DESTROYNOTIFY(gtk_widget_unref)); gtk_object_set_data_full (GTK_OBJECT(PATCH_WINDOW), "bAPPLY", patch_win.bAPPLY, GTK_DESTROYNOTIFY(gtk_widget_unref)); gtk_object_set_data_full (GTK_OBJECT(PATCH_WINDOW), "bREPORT", patch_win.bREPORT, GTK_DESTROYNOTIFY(gtk_widget_unref)); + gtk_object_set_data_full (GTK_OBJECT(PATCH_WINDOW), "bBASEDIR", patch_win.bBASEDIR, GTK_DESTROYNOTIFY(gtk_widget_unref)); + gtk_object_set_data_full (GTK_OBJECT(PATCH_WINDOW), "dir_Entry", patch_win.dir_Entry, GTK_DESTROYNOTIFY(gtk_widget_unref)); gtk_object_set_data_full (GTK_OBJECT(PATCH_WINDOW), "TxtWindow", TxtWindow, GTK_DESTROYNOTIFY(gtk_widget_unref)); gtk_object_set_data_full (GTK_OBJECT(PATCH_WINDOW), "LOGVIEW", patch_win.LOGVIEW, GTK_DESTROYNOTIFY(gtk_widget_unref)); gtk_object_set_data_full (GTK_OBJECT(PATCH_WINDOW), "bCLOSE", patch_win.bCLOSE, GTK_DESTROYNOTIFY(gtk_widget_unref)); @@ -694,6 +795,8 @@ static void create_window2 (GtkWidget *u gtk_signal_connect (GTK_OBJECT(PATCH_WINDOW), "destroy", GTK_SIGNAL_FUNC(destroy_window2), NULL); gtk_signal_connect (GTK_OBJECT(patch_win.bCLOSE), "clicked", GTK_SIGNAL_FUNC(destroy_window2), NULL); + gtk_signal_connect (GTK_OBJECT(patch_win.bBASEDIR), "toggled", GTK_SIGNAL_FUNC(basedir_Change), NULL); + gtk_signal_connect (GTK_OBJECT(patch_win.dir_Entry), "changed", GTK_SIGNAL_FUNC(basedir_ChangePath), NULL); #if !defined(DEMOBUILD) gtk_signal_connect (GTK_OBJECT(patch_win.bAPPLY), "clicked", GTK_SIGNAL_FUNC(start_xpatch), &patch_win); gtk_signal_connect (GTK_OBJECT(patch_win.bREPORT), "clicked", GTK_SIGNAL_FUNC(report_status), &patch_win); @@ -741,10 +844,6 @@ static void create_window1 (void) GtkWidget *TxtPatch; // Data patch label GtkWidget *bPATCH; // PATCH button GtkWidget *SRATE_Entry; // Sampling rate listing - GtkWidget *H2G_Entry; // Hexen2 games listing -#ifndef DEMOBUILD - GtkWidget *HWG_Entry; // Hexenworld games listing -#endif /* DEMOBUILD */ // Separators GtkWidget *hseparator0; @@ -1039,9 +1138,8 @@ static void create_window1 (void) gtk_widget_set_size_request (WGT_H2GAME, 172, 32); #ifndef DEMOBUILD TmpList = NULL; - TmpList = g_list_append (TmpList, (gpointer) "( None )"); gtk_combo_set_use_arrows (GTK_COMBO(WGT_H2GAME), FALSE); - for (i = 1; i < MAX_H2GAMES; i++) + for (i = 0; i < MAX_H2GAMES; i++) { if (h2game_names[i].available) TmpList = g_list_append (TmpList, h2game_names[i].name); @@ -1075,9 +1173,8 @@ static void create_window1 (void) gtk_widget_ref (WGT_HWGAME); gtk_widget_set_size_request (WGT_HWGAME, 172, 32); TmpList = NULL; - TmpList = g_list_append (TmpList, (gpointer) "Plain DeathMatch"); gtk_combo_set_use_arrows (GTK_COMBO(WGT_HWGAME), FALSE); - for (i = 1; i < MAX_HWGAMES; i++) + for (i = 0; i < MAX_HWGAMES; i++) { if (hwgame_names[i].available) TmpList = g_list_append (TmpList, hwgame_names[i].name); diff -urNp uhexen2-20070523/launcher/launch_bin.c uhexen2-20070531/launcher/launch_bin.c --- uhexen2-20070523/launcher/launch_bin.c 2007-04-15 23:40:38.000000000 +0300 +++ uhexen2-20070531/launcher/launch_bin.c 2007-05-31 19:27:03.000000000 +0300 @@ -86,6 +86,12 @@ void launch_hexen2_bin (void) i = 0; args[i] = binary_name; + if (basedir_nonstd && game_basedir[0]) + { + args[++i] = "-basedir"; + args[++i] = game_basedir; + } + #if !defined(DEMOBUILD) if (destiny == DEST_H2 && mp_support) args[++i] = "-portals"; diff -urNp uhexen2-20070523/launcher/launcher_defs.h uhexen2-20070531/launcher/launcher_defs.h --- uhexen2-20070523/launcher/launcher_defs.h 2007-04-15 00:30:16.000000000 +0300 +++ uhexen2-20070531/launcher/launcher_defs.h 2007-05-31 19:27:03.000000000 +0300 @@ -40,7 +40,7 @@ // Launcher version num. #define LAUNCHER_VERSION_MAJ 1 #define LAUNCHER_VERSION_MID 0 -#define LAUNCHER_VERSION_MIN 0 +#define LAUNCHER_VERSION_MIN 1 #define LAUNCHER_VERSION_STR STRINGIFY(LAUNCHER_VERSION_MAJ) "." STRINGIFY(LAUNCHER_VERSION_MID) "." STRINGIFY(LAUNCHER_VERSION_MIN) #ifndef DEMOBUILD diff -urNp uhexen2-20070523/launcher/main.c uhexen2-20070531/launcher/main.c --- uhexen2-20070523/launcher/main.c 2007-04-15 00:30:16.000000000 +0300 +++ uhexen2-20070531/launcher/main.c 2007-05-31 19:27:03.000000000 +0300 @@ -206,6 +206,7 @@ int main (int argc, char *argv[]) // go into the binary's directory chdir (basedir); + cfg_read_basedir(); scan_game_installation(); read_config_file(); diff -urNp uhexen2-20070523/launcher/widget_defs.h uhexen2-20070531/launcher/widget_defs.h --- uhexen2-20070523/launcher/widget_defs.h 2007-04-15 00:30:17.000000000 +0300 +++ uhexen2-20070531/launcher/widget_defs.h 2007-05-31 19:27:03.000000000 +0300 @@ -49,10 +49,12 @@ typedef struct { GtkWidget *mywindow; // Main window GtkWidget *fixed1; // Widgets container + GtkWidget *bBASEDIR; // Use a different game basedir GtkWidget *bCLOSE; // Close button GtkWidget *bAPPLY; // Apply patch button GtkWidget *bREPORT; // Report installation status GtkWidget *LOGVIEW; // LogEntry line for patch process + GtkWidget *dir_Entry; // path for game basedir GtkWidget *StatusBar; // Status bar, (patch status) gint statbar_id; // statbar context id guint delete_handler;