Skip to content

Commit 98d3fe7

Browse files
committed
Installer: per-version shortcuts; ask for the Quake folder if not found
The store-detection port (f75b0e6) silently dropped -multiuser from the installer shortcut, moving configs and saves from %APPDATA%\vkQuake into the game directory, and left users without a Steam/GOG/Epic install staring at the missing-data error (#943). The installer now creates "vkQuake (classic)" and "vkQuake (remastered)" start menu entries again, passing -multiuser plus -original/-remastered so each shortcut launches its version directly instead of prompting every start when both are installed. When the requested version is not found by store detection, the engine opens a native folder picker (IFileOpenDialog on Windows) and remembers a valid selection in basedirs.txt in the pref dir; a folder without the required data (id1/pak0.pak or QuakeEX.kpf per flavor) shows a warning and asks again without storing it. A bare launch with no data found at all gets the same dialog instead of the missing-data error. Detected store paths are now validated per flavor as well. Fixes #943
1 parent 8fed52d commit 98d3fe7

18 files changed

Lines changed: 310 additions & 23 deletions

Packaging/Windows/vkQuake.nsi

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ Section "Game" GAME
5959

6060
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
6161
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
62-
# the engine locates Steam/GOG/Epic Quake installs at startup, no -basedir needed
63-
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\vkQuake.lnk" $OUTDIR\vkQuake.exe "" \
62+
# the engine locates Steam/GOG/Epic Quake installs at startup or asks for the folder, no -basedir needed
63+
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\vkQuake (classic).lnk" $OUTDIR\vkQuake.exe "-multiuser -original" \
6464
"$OUTDIR\vkQuake.exe" "" "" "" ""
65-
CreateShortCut "$INSTDIR\vkQuake.lnk" $OUTDIR\vkQuake.exe "" \
65+
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\vkQuake (rerelease).lnk" $OUTDIR\vkQuake.exe "-multiuser -remastered" \
66+
"$OUTDIR\vkQuake.exe" "" "" "" ""
67+
CreateShortCut "$INSTDIR\vkQuake (classic).lnk" $OUTDIR\vkQuake.exe "-multiuser -original" \
68+
"$OUTDIR\vkQuake.exe" "" "" "" ""
69+
CreateShortCut "$INSTDIR\vkQuake (rerelease).lnk" $OUTDIR\vkQuake.exe "-multiuser -remastered" \
6670
"$OUTDIR\vkQuake.exe" "" "" "" ""
6771
!insertmacro MUI_STARTMENU_WRITE_END
6872
SectionEnd
@@ -88,7 +92,8 @@ Function ${UN}Clean
8892
DeleteRegKey HKLM "Software\vkQuake"
8993
RMDir /r $INSTDIR
9094
!insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder
91-
Delete "$SMPROGRAMS\$StartMenuFolder\vkQuake.lnk"
95+
Delete "$SMPROGRAMS\$StartMenuFolder\vkQuake (classic).lnk"
96+
Delete "$SMPROGRAMS\$StartMenuFolder\vkQuake (remastered).lnk"
9297
RMDir "$SMPROGRAMS\$StartMenuFolder"
9398
SetAutoClose true
9499
FunctionEnd

Quake/common.c

Lines changed: 239 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,31 +2715,189 @@ static void COM_Game_f (void)
27152715

27162716
/*
27172717
=================
2718-
COM_IsValidBaseDir
2718+
COM_IsValidFlavorDir
27192719
2720-
Returns true if the directory contains usable game data:
2721-
either classic id1/pak0.pak or the rerelease QuakeEX.kpf
2720+
Returns true if the directory contains usable game data for the given
2721+
flavor: classic id1/pak0.pak or the rerelease QuakeEX.kpf (-1 accepts
2722+
either)
27222723
=================
27232724
*/
2724-
static qboolean COM_IsValidBaseDir (const char *dir)
2725+
static qboolean COM_IsValidFlavorDir (const char *dir, int flavor)
27252726
{
27262727
char path[MAX_OSPATH];
27272728

2728-
if ((size_t)q_snprintf (path, sizeof (path), "%s/" GAMENAME "/pak0.pak", dir) < sizeof (path) && Sys_FileType (path) == FS_ENT_FILE)
2729+
if (flavor != QUAKE_FLAVOR_REMASTERED && (size_t)q_snprintf (path, sizeof (path), "%s/" GAMENAME "/pak0.pak", dir) < sizeof (path) &&
2730+
Sys_FileType (path) == FS_ENT_FILE)
27292731
return true;
2730-
if ((size_t)q_snprintf (path, sizeof (path), "%s/QuakeEX.kpf", dir) < sizeof (path) && Sys_FileType (path) == FS_ENT_FILE)
2732+
if (flavor != QUAKE_FLAVOR_ORIGINAL && (size_t)q_snprintf (path, sizeof (path), "%s/QuakeEX.kpf", dir) < sizeof (path) &&
2733+
Sys_FileType (path) == FS_ENT_FILE)
27312734
return true;
27322735

27332736
return false;
27342737
}
27352738

2739+
static qboolean COM_IsValidBaseDir (const char *dir)
2740+
{
2741+
return COM_IsValidFlavorDir (dir, -1);
2742+
}
2743+
2744+
/*
2745+
=================
2746+
COM_RequestedQuakeFlavor
2747+
2748+
Quake version requested on the command line, -1 if none
2749+
=================
2750+
*/
2751+
static int COM_RequestedQuakeFlavor (void)
2752+
{
2753+
if (COM_CheckParm ("-prefremaster") || COM_CheckParm ("-remaster") || COM_CheckParm ("-remastered"))
2754+
return QUAKE_FLAVOR_REMASTERED;
2755+
if (COM_CheckParm ("-preforiginal") || COM_CheckParm ("-original"))
2756+
return QUAKE_FLAVOR_ORIGINAL;
2757+
return -1;
2758+
}
2759+
2760+
#ifdef USE_SDL3
2761+
/*
2762+
=================
2763+
COM_LoadSelectedBaseDirs / COM_SaveSelectedBaseDirs
2764+
2765+
Game folders the user picked in the folder dialog, kept in the pref
2766+
dir. A new pick is only written once the engine is fully initialized
2767+
(COM_WriteSelectedBaseDir) so a folder with broken data can't get
2768+
remembered.
2769+
=================
2770+
*/
2771+
static int com_pendingbasedirflavor = -1;
2772+
static char com_pendingbasedir[MAX_OSPATH];
2773+
2774+
static void COM_LoadSelectedBaseDirs (char *original, size_t originalsize, char *remastered, size_t remasteredsize)
2775+
{
2776+
char line[MAX_OSPATH + 16];
2777+
FILE *f;
2778+
char *pref_path = SDL_GetPrefPath ("", "vkQuake");
2779+
2780+
f = fopen (va ("%s/basedirs.txt", pref_path), "r");
2781+
SDL_free (pref_path);
2782+
if (!f)
2783+
return;
2784+
2785+
while (fgets (line, sizeof (line), f))
2786+
{
2787+
char *path = strchr (line, ' ');
2788+
if (!path)
2789+
continue;
2790+
*path++ = '\0';
2791+
path[strcspn (path, "\r\n")] = '\0';
2792+
if (!strcmp (line, "classic"))
2793+
q_strlcpy (original, path, originalsize);
2794+
else if (!strcmp (line, "remastered"))
2795+
q_strlcpy (remastered, path, remasteredsize);
2796+
}
2797+
2798+
fclose (f);
2799+
}
2800+
2801+
static void COM_SaveSelectedBaseDirs (const char *original, const char *remastered)
2802+
{
2803+
FILE *f;
2804+
char *pref_path = SDL_GetPrefPath ("", "vkQuake");
2805+
2806+
f = fopen (va ("%s/basedirs.txt", pref_path), "w");
2807+
SDL_free (pref_path);
2808+
if (!f)
2809+
return;
2810+
2811+
if (original[0])
2812+
fprintf (f, "classic %s\n", original);
2813+
if (remastered[0])
2814+
fprintf (f, "remastered %s\n", remastered);
2815+
2816+
fclose (f);
2817+
}
2818+
2819+
/*
2820+
=================
2821+
COM_SelectBaseDir
2822+
2823+
Asks the user for a game folder until it contains data for the wanted
2824+
flavor (-1 accepts either); returns false if the dialog was cancelled
2825+
=================
2826+
*/
2827+
static qboolean COM_SelectBaseDir (int flavor, const char *default_location, char *dst, size_t dstsize)
2828+
{
2829+
const char *title, *complaint;
2830+
2831+
switch (flavor)
2832+
{
2833+
case QUAKE_FLAVOR_ORIGINAL:
2834+
title = "Select your classic Quake folder";
2835+
complaint = "The selected folder does not contain " GAMENAME "/pak0.pak.";
2836+
break;
2837+
case QUAKE_FLAVOR_REMASTERED:
2838+
title = "Select your remastered Quake folder";
2839+
complaint = "The selected folder does not contain QuakeEX.kpf.";
2840+
break;
2841+
default:
2842+
title = "Select your Quake folder";
2843+
complaint = "The selected folder does not contain Quake game data (" GAMENAME "/pak0.pak or QuakeEX.kpf).";
2844+
break;
2845+
}
2846+
2847+
while (Sys_SelectFolder (title, default_location, dst, dstsize))
2848+
{
2849+
if (COM_IsValidFlavorDir (dst, flavor))
2850+
return true;
2851+
SDL_ShowSimpleMessageBox (SDL_MESSAGEBOX_WARNING, "vkQuake", complaint, NULL);
2852+
}
2853+
2854+
return false;
2855+
}
2856+
2857+
static void COM_SetPendingBaseDir (int flavor, const char *dir)
2858+
{
2859+
com_pendingbasedirflavor = flavor;
2860+
q_strlcpy (com_pendingbasedir, dir, sizeof (com_pendingbasedir));
2861+
}
2862+
#endif
2863+
2864+
/*
2865+
=================
2866+
COM_WriteSelectedBaseDir
2867+
2868+
Remembers the folder picked in the dialog; called once the engine is
2869+
fully initialized as proof the folder contains working game data
2870+
=================
2871+
*/
2872+
void COM_WriteSelectedBaseDir (void)
2873+
{
2874+
#ifdef USE_SDL3
2875+
char original[MAX_OSPATH] = {0};
2876+
char remastered[MAX_OSPATH] = {0};
2877+
2878+
if (com_pendingbasedirflavor < 0)
2879+
return;
2880+
2881+
COM_LoadSelectedBaseDirs (original, sizeof (original), remastered, sizeof (remastered));
2882+
if (com_pendingbasedirflavor == QUAKE_FLAVOR_REMASTERED)
2883+
q_strlcpy (remastered, com_pendingbasedir, sizeof (remastered));
2884+
else
2885+
q_strlcpy (original, com_pendingbasedir, sizeof (original));
2886+
COM_SaveSelectedBaseDirs (original, remastered);
2887+
2888+
com_pendingbasedirflavor = -1;
2889+
#endif
2890+
}
2891+
27362892
/*
27372893
=================
27382894
COM_FindStoreBaseDir
27392895
27402896
Locates a Steam/GOG/Epic Games Store install of Quake and points
27412897
com_basedir at it (based on the Ironwail startup flow). Used when
27422898
the working directory has no game data and no -basedir was given.
2899+
Asks the user for the folder when the requested version isn't found,
2900+
starting at the previously picked folder.
27432901
=================
27442902
*/
27452903
static void COM_FindStoreBaseDir (void)
@@ -2748,6 +2906,7 @@ static void COM_FindStoreBaseDir (void)
27482906
char original[MAX_OSPATH] = {0};
27492907
char remastered[MAX_OSPATH] = {0};
27502908
quakeflavor_t flavor;
2909+
int requested;
27512910
qboolean force_steam = COM_CheckParm ("-steam") != 0;
27522911
qboolean force_gog = COM_CheckParm ("-gog") != 0;
27532912
qboolean force_egs = (COM_CheckParm ("-egs") || COM_CheckParm ("-epic")) != 0;
@@ -2776,11 +2935,77 @@ static void COM_FindStoreBaseDir (void)
27762935
remastered[0] = '\0';
27772936
}
27782937

2779-
if (original[0] && !COM_IsValidBaseDir (original))
2938+
if (original[0] && !COM_IsValidFlavorDir (original, QUAKE_FLAVOR_ORIGINAL))
27802939
original[0] = '\0';
2781-
if (remastered[0] && !COM_IsValidBaseDir (remastered))
2940+
if (remastered[0] && !COM_IsValidFlavorDir (remastered, QUAKE_FLAVOR_REMASTERED))
27822941
remastered[0] = '\0';
27832942

2943+
requested = COM_RequestedQuakeFlavor ();
2944+
2945+
if (!forced && !isDedicated)
2946+
{
2947+
#ifdef USE_SDL3
2948+
char stored_original[MAX_OSPATH] = {0};
2949+
char stored_remastered[MAX_OSPATH] = {0};
2950+
2951+
COM_LoadSelectedBaseDirs (stored_original, sizeof (stored_original), stored_remastered, sizeof (stored_remastered));
2952+
2953+
// use the folder picked in a previous run unless the user wants a new one
2954+
if (!COM_CheckParm ("-select-basedir"))
2955+
{
2956+
if (!original[0] && stored_original[0] && COM_IsValidFlavorDir (stored_original, QUAKE_FLAVOR_ORIGINAL))
2957+
q_strlcpy (original, stored_original, sizeof (original));
2958+
if (!remastered[0] && stored_remastered[0] && COM_IsValidFlavorDir (stored_remastered, QUAKE_FLAVOR_REMASTERED))
2959+
q_strlcpy (remastered, stored_remastered, sizeof (remastered));
2960+
}
2961+
2962+
// still missing: ask for the folder, starting the dialog at the previous pick
2963+
if (requested == QUAKE_FLAVOR_ORIGINAL && !original[0])
2964+
{
2965+
if (!COM_SelectBaseDir (QUAKE_FLAVOR_ORIGINAL, stored_original, original, sizeof (original)))
2966+
{
2967+
SDL_Quit ();
2968+
exit (0);
2969+
}
2970+
COM_SetPendingBaseDir (QUAKE_FLAVOR_ORIGINAL, original);
2971+
}
2972+
else if (requested == QUAKE_FLAVOR_REMASTERED && !remastered[0])
2973+
{
2974+
if (!COM_SelectBaseDir (QUAKE_FLAVOR_REMASTERED, stored_remastered, remastered, sizeof (remastered)))
2975+
{
2976+
SDL_Quit ();
2977+
exit (0);
2978+
}
2979+
COM_SetPendingBaseDir (QUAKE_FLAVOR_REMASTERED, remastered);
2980+
}
2981+
else if (requested < 0 && !original[0] && !remastered[0])
2982+
{
2983+
char selected[MAX_OSPATH];
2984+
if (!COM_SelectBaseDir (-1, stored_remastered[0] ? stored_remastered : stored_original, selected, sizeof (selected)))
2985+
{
2986+
SDL_Quit ();
2987+
exit (0);
2988+
}
2989+
if (COM_IsValidFlavorDir (selected, QUAKE_FLAVOR_REMASTERED))
2990+
{
2991+
q_strlcpy (remastered, selected, sizeof (remastered));
2992+
COM_SetPendingBaseDir (QUAKE_FLAVOR_REMASTERED, selected);
2993+
}
2994+
else
2995+
{
2996+
q_strlcpy (original, selected, sizeof (original));
2997+
COM_SetPendingBaseDir (QUAKE_FLAVOR_ORIGINAL, selected);
2998+
}
2999+
}
3000+
#else
3001+
// no folder picker without the SDL3 dialog API
3002+
if (requested == QUAKE_FLAVOR_ORIGINAL && !original[0])
3003+
Sys_Error ("Couldn't find the classic Quake folder. Use -basedir to specify it.");
3004+
else if (requested == QUAKE_FLAVOR_REMASTERED && !remastered[0])
3005+
Sys_Error ("Couldn't find the remastered Quake folder. Use -basedir to specify it.");
3006+
#endif
3007+
}
3008+
27843009
if (!original[0] && !remastered[0])
27853010
{
27863011
if (force_steam)
@@ -2792,15 +3017,12 @@ static void COM_FindStoreBaseDir (void)
27923017
return; // fall through to the regular missing-data error
27933018
}
27943019

2795-
if (original[0] && remastered[0])
2796-
{
2797-
if (COM_CheckParm ("-prefremaster") || COM_CheckParm ("-remaster") || COM_CheckParm ("-remastered"))
2798-
flavor = QUAKE_FLAVOR_REMASTERED;
2799-
else if (COM_CheckParm ("-preforiginal") || COM_CheckParm ("-original"))
2800-
flavor = QUAKE_FLAVOR_ORIGINAL;
2801-
else
2802-
flavor = ChooseQuakeFlavor ();
2803-
}
3020+
if (requested == QUAKE_FLAVOR_REMASTERED && remastered[0])
3021+
flavor = QUAKE_FLAVOR_REMASTERED;
3022+
else if (requested == QUAKE_FLAVOR_ORIGINAL && original[0])
3023+
flavor = QUAKE_FLAVOR_ORIGINAL;
3024+
else if (original[0] && remastered[0])
3025+
flavor = ChooseQuakeFlavor ();
28043026
else
28053027
flavor = remastered[0] ? QUAKE_FLAVOR_REMASTERED : QUAKE_FLAVOR_ORIGINAL;
28063028

Quake/common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ int COM_CheckParm (const char *parm);
314314
void COM_Init (void);
315315
void COM_InitArgv (int argc, char **argv);
316316
void COM_InitFilesystem (void);
317+
void COM_WriteSelectedBaseDir (void);
317318

318319
const char *COM_SkipPath (const char *pathname);
319320
void COM_StripExtension (const char *in, char *out, size_t outsize);

Quake/host.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,10 @@ void Host_Init (void)
12241224
host_initialized = true;
12251225
Con_Printf ("\n========= Quake Initialized =========\n\n");
12261226

1227+
// the folder from the selection dialog is only remembered now,
1228+
// with the game data proven to actually work
1229+
COM_WriteSelectedBaseDir ();
1230+
12271231
if (cls.state != ca_dedicated)
12281232
{
12291233
Cbuf_InsertText ("exec quake.rc\n");

Quake/sys.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ const char *Sys_GetEGSLauncherData (void); // Mem_Alloc'ed buffer, caller Mem_Fr
116116
// steamlibrary is only needed on Linux (proton prefix) and may be NULL
117117
qboolean Sys_GetNightdiveUserDir (char *path, size_t pathsize, const char *steamlibrary);
118118

119+
#ifdef USE_SDL3
120+
// folder picker (SDL3 file dialog); starts at default_location if non-NULL,
121+
// returns false when cancelled
122+
qboolean Sys_SelectFolder (const char *title, const char *default_location, char *dst, size_t dstsize);
123+
#endif
124+
119125
//
120126
// system IO
121127
//

0 commit comments

Comments
 (0)