XDG home directory support

This commit is contained in:
Tim Angus 2025-09-01 20:00:14 +01:00
parent da4f9bd13d
commit 0ebf1df742
18 changed files with 527 additions and 143 deletions

View File

@ -202,6 +202,31 @@ set using command line arguments:
ioquake3 +set cl_renderer opengl2 +set r_preferOpenGLES 1
# Filesystem
Compared to the original release, user configuration and data files are stored
in more modern locations. If you want a different behaviour a specific path
can be provided by adding `+set fs_homepath <path>` to the command line.
### Windows
`C:\Users\<username>\AppData\Roaming\Quake3`
### macOS
`/Users/<username>/Library/Application Support/Quake3`
### Linux
`/home/<username>/.config/Quake3` Configuration files.
`/home/<username>/.local/share/Quake3` Data files (pk3s etc.).
`/home/<username>/.local/state/Quake3` Other internal runtime files.
These directories correspond to the Free Desktop XDG Base Directory
Specification. The original release used `/home/.q3a`. This will be used if
present, however in this case a prompt will be shown suggesting migration to
the above locations, if desired.
# Console
## New cvars
@ -480,9 +505,8 @@ binary must not detect any original quake3 game pak files. If this
condition is met, the game will set com_standalone to 1 and is then running
in stand alone mode.
If you want the engine to use a different directory in your homepath than
e.g. "Quake3" on Windows or ".q3a" on Linux, then set a new name at startup
by adding
If you want the engine to use a different directory in your homepaths than
"Quake3" then set a new name at startup by adding
+set com_homepath <homedirname>
@ -496,7 +520,7 @@ matching game name.
Example line:
+set com_basegame basefoo +set com_homepath .foo
+set com_basegame basefoo +set com_homepath foo
+set com_gamename foo
If you really changed parts that would make vanilla ioquake3 incompatible with

View File

@ -71,7 +71,7 @@ void Log_Open(char *filename)
botimport.Print(PRT_ERROR, "log file %s is already opened\n", logfile.filename);
return;
} //end if
ospath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), Cvar_VariableString("fs_game"), filename);
ospath = FS_BuildOSPath(Cvar_VariableString("fs_homestatepath"), Cvar_VariableString("fs_game"), filename);
logfile.fp = fopen(ospath, "wb");
if (!logfile.fp)
{

View File

@ -335,10 +335,10 @@ qboolean CL_OpenAVIForWriting( const char *fileName )
return qfalse;
}
if( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 )
if( ( afd.f = FS_FOpenFileWrite_HomeData( fileName ) ) <= 0 )
return qfalse;
if( ( afd.idxF = FS_FOpenFileWrite(
if( ( afd.idxF = FS_FOpenFileWrite_HomeData(
va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 )
{
FS_FCloseFile( afd.f );
@ -635,7 +635,7 @@ qboolean CL_CloseAVI( void )
FS_FCloseFile( afd.idxF );
// Remove temp index file
FS_HomeRemove( idxFileName );
FS_Remove_HomeData( idxFileName );
// Write the real header
FS_Seek( afd.f, 0, FS_SEEK_SET );

View File

@ -201,7 +201,7 @@ void Con_Dump_f (void)
return;
}
f = FS_FOpenFileWrite( filename );
f = FS_FOpenFileWrite_HomeData( filename );
if (!f)
{
Com_Printf ("ERROR: couldn't open %s.\n", filename);

View File

@ -1577,7 +1577,7 @@ void CL_SaveConsoleHistory( void )
consoleSaveBufferSize = strlen( consoleSaveBuffer );
f = FS_FOpenFileWrite( CONSOLE_HISTORY_FILE );
f = FS_FOpenFileWrite_HomeState( CONSOLE_HISTORY_FILE );
if( !f )
{
Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE );

View File

@ -742,7 +742,7 @@ void CL_Record_f( void ) {
#endif
Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer);
if (!FS_FileExists(name))
if (!FS_FileExists_HomeData(name))
break; // file doesn't exist
}
}
@ -750,7 +750,7 @@ void CL_Record_f( void ) {
// open the demo file
Com_Printf ("recording to %s.\n", name);
clc.demofile = FS_FOpenFileWrite( name );
clc.demofile = FS_FOpenFileWrite_HomeData( name );
if ( !clc.demofile ) {
Com_Printf ("ERROR: couldn't open.\n");
return;
@ -903,7 +903,7 @@ void CL_DemoCompleted( void )
else
numFrames = clc.timeDemoFrames - 1;
f = FS_FOpenFileWrite( cl_timedemoLog->string );
f = FS_FOpenFileWrite_HomeData( cl_timedemoLog->string );
if( f )
{
FS_Printf( f, "# %s", buffer );
@ -2197,7 +2197,7 @@ static void CL_BeginHttpDownload( const char *remoteURL ) {
CL_HTTP_BeginDownload(remoteURL);
Q_strncpyz(clc.downloadURL, remoteURL, sizeof(clc.downloadURL));
clc.download = FS_BaseDir_FOpenFileWrite(clc.downloadTempName);
clc.download = FS_BaseDir_FOpenFileWrite_HomeData(clc.downloadTempName);
if(!clc.download) {
Com_Error(ERR_DROP, "CL_BeginHTTPDownload: failed to open "
"%s for writing", clc.downloadTempName);
@ -2233,7 +2233,7 @@ void CL_NextDownload(void)
// A download has finished, check whether this matches a referenced checksum
if(*clc.downloadName)
{
char *zippath = FS_BaseDir_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName);
char *zippath = FS_BaseDir_BuildOSPath(Cvar_VariableString("fs_homedatapath"), clc.downloadName);
if(!FS_CompareZipChecksum(zippath))
Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName);
@ -2977,7 +2977,7 @@ void CL_Frame ( int msec ) {
clc.download = 0;
}
FS_BaseDir_Rename(clc.downloadTempName, clc.downloadName, qfalse);
FS_BaseDir_Rename_HomeData(clc.downloadTempName, clc.downloadName, qfalse);
clc.downloadRestart = qtrue;
CL_NextDownload();
}
@ -3307,7 +3307,7 @@ void CL_InitRef( void ) {
ri.FS_FreeFileList = FS_FreeFileList;
ri.FS_ListFiles = FS_ListFiles;
ri.FS_FileIsInPAK = FS_FileIsInPAK;
ri.FS_FileExists = FS_FileExists;
ri.FS_FileExists = FS_FileExists_HomeData;
ri.Cvar_Get = Cvar_Get;
ri.Cvar_Set = Cvar_Set;
ri.Cvar_SetValue = Cvar_SetValue;
@ -3418,7 +3418,7 @@ void CL_Video_f( void )
Com_sprintf( filename, MAX_OSPATH, "videos/video%d%d%d%d.avi",
a, b, c, d );
if( !FS_FileExists( filename ) )
if( !FS_FileExists_HomeData( filename ) )
break; // file doesn't exist
}
@ -3471,7 +3471,7 @@ static void CL_GenerateQKey(void)
Com_Printf( "QKEY building random string\n" );
Com_RandomBytes( buff, sizeof(buff) );
f = FS_BaseDir_FOpenFileWrite( QKEY_FILE );
f = FS_BaseDir_FOpenFileWrite_HomeState( QKEY_FILE );
if( !f ) {
Com_Printf( "QKEY could not open %s for write\n",
QKEY_FILE );

View File

@ -608,7 +608,7 @@ void CL_ParseDownload ( msg_t *msg ) {
// open the file if not opened yet
if (!clc.download)
{
clc.download = FS_BaseDir_FOpenFileWrite( clc.downloadTempName );
clc.download = FS_BaseDir_FOpenFileWrite_HomeData( clc.downloadTempName );
if (!clc.download) {
Com_Printf( "Could not create %s\n", clc.downloadTempName );
@ -635,7 +635,7 @@ void CL_ParseDownload ( msg_t *msg ) {
clc.download = 0;
// rename the file
FS_BaseDir_Rename ( clc.downloadTempName, clc.downloadName, qfalse );
FS_BaseDir_Rename_HomeData ( clc.downloadTempName, clc.downloadName, qfalse );
}
// send intentions now

View File

@ -74,7 +74,7 @@ LAN_SaveServersToCache
*/
void LAN_SaveServersToCache( void ) {
int size;
fileHandle_t fileOut = FS_BaseDir_FOpenFileWrite("servercache.dat");
fileHandle_t fileOut = FS_BaseDir_FOpenFileWrite_HomeState("servercache.dat");
FS_Write(&cls.numglobalservers, sizeof(int), fileOut);
FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut);
size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers);

View File

@ -205,7 +205,7 @@ void QDECL Com_Printf( const char *fmt, ... ) {
time( &aclock );
newtime = localtime( &aclock );
logfile = FS_FOpenFileWrite( "qconsole.log" );
logfile = FS_FOpenFileWrite_HomeData( "qconsole.log" );
if(logfile)
{
@ -1921,8 +1921,8 @@ void Com_InitJournaling( void ) {
if ( com_journal->integer == 1 ) {
Com_Printf( "Journaling events\n");
com_journalFile = FS_FOpenFileWrite( "journal.dat" );
com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" );
com_journalFile = FS_FOpenFileWrite_HomeState( "journal.dat" );
com_journalDataFile = FS_FOpenFileWrite_HomeState( "journaldata.dat" );
} else if ( com_journal->integer == 2 ) {
Com_Printf( "Replaying journaled events\n");
FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue );
@ -2547,7 +2547,7 @@ static void Com_WriteCDKey( const char *filename, const char *ikey ) {
#ifndef _WIN32
savedumask = umask(0077);
#endif
f = FS_BaseDir_FOpenFileWrite( fbuffer );
f = FS_BaseDir_FOpenFileWrite_HomeState( fbuffer );
if ( !f ) {
Com_Printf ("Couldn't write CD key to %s.\n", fbuffer );
goto out;
@ -2921,7 +2921,7 @@ void Com_ReadFromPipe( void )
void Com_WriteConfigToFile( const char *filename ) {
fileHandle_t f;
f = FS_FOpenFileWrite( filename );
f = FS_FOpenFileWrite_HomeConfig( filename );
if ( !f ) {
Com_Printf ("Couldn't write %s.\n", filename );
return;
@ -3293,7 +3293,7 @@ void Com_Shutdown (void) {
if( pipefile ) {
FS_FCloseFile( pipefile );
FS_HomeRemove( com_pipefile->string );
FS_Remove_HomeData( com_pipefile->string );
}
}

View File

@ -246,7 +246,9 @@ typedef struct searchpath_s {
static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
static cvar_t *fs_debug;
static cvar_t *fs_homepath;
static cvar_t *fs_homeconfigpath;
static cvar_t *fs_homedatapath;
static cvar_t *fs_homestatepath;
static cvar_t *fs_apppath;
static cvar_t *fs_steampath;
@ -609,14 +611,14 @@ void FS_Remove( const char *osPath ) {
/*
===========
FS_HomeRemove
FS_Remove_HomeData
===========
*/
void FS_HomeRemove( const char *homePath ) {
void FS_Remove_HomeData( const char *homePath ) {
FS_CheckFilenameIsMutable( homePath, __func__ );
remove( FS_BuildOSPath( fs_homepath->string,
remove( FS_BuildOSPath( fs_homedatapath->string,
fs_gamedir, homePath ) );
}
@ -644,7 +646,7 @@ qboolean FS_FileInPathExists(const char *testpath)
/*
================
FS_FileExists
FS_FileExists_HomeData
Tests if the file exists in the current gamedir, this DOES NOT
search the paths. This is to determine if opening a file to write
@ -652,21 +654,21 @@ search the paths. This is to determine if opening a file to write
NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
================
*/
qboolean FS_FileExists(const char *file)
qboolean FS_FileExists_HomeData(const char *file)
{
return FS_FileInPathExists(FS_BuildOSPath(fs_homepath->string, fs_gamedir, file));
return FS_FileInPathExists(FS_BuildOSPath(fs_homedatapath->string, fs_gamedir, file));
}
/*
================
FS_BaseDir_FileExists
FS_BaseDir_FileExists_HomeData
Tests if the file exists
================
*/
qboolean FS_BaseDir_FileExists( const char *file )
static qboolean FS_BaseDir_FileExists_HomeData( const char *file )
{
return FS_FileInPathExists(FS_BaseDir_BuildOSPath(fs_homepath->string, file));
return FS_FileInPathExists(FS_BaseDir_BuildOSPath(fs_homedatapath->string, file));
}
@ -708,13 +710,32 @@ static fileHandle_t FS_OSPath_FOpenFileWrite( const char *ospath, const char *fi
/*
===========
FS_BaseDir_FOpenFileWrite
FS_BaseDir_FOpenFileWrite_HomeConfig
===========
*/
fileHandle_t FS_BaseDir_FOpenFileWrite( const char *filename ) {
fileHandle_t FS_BaseDir_FOpenFileWrite_HomeConfig( const char *filename ) {
return FS_OSPath_FOpenFileWrite(
FS_BaseDir_BuildOSPath(fs_homepath->string, filename), filename);
FS_BaseDir_BuildOSPath(fs_homeconfigpath->string, filename), filename);
}
/*
===========
FS_BaseDir_FOpenFileWrite_HomeData
===========
*/
fileHandle_t FS_BaseDir_FOpenFileWrite_HomeData( const char *filename ) {
return FS_OSPath_FOpenFileWrite(
FS_BaseDir_BuildOSPath(fs_homedatapath->string, filename), filename);
}
/*
===========
FS_BaseDir_FOpenFileWrite_HomeState
===========
*/
fileHandle_t FS_BaseDir_FOpenFileWrite_HomeState( const char *filename ) {
return FS_OSPath_FOpenFileWrite(
FS_BaseDir_BuildOSPath(fs_homestatepath->string, filename), filename);
}
/*
@ -776,11 +797,11 @@ long FS_BaseDir_FOpenFileRead(const char *filename, fileHandle_t *fp)
/*
===========
FS_BaseDir_Rename
FS_BaseDir_Rename_HomeData
===========
*/
void FS_BaseDir_Rename( const char *from, const char *to, qboolean safe ) {
void FS_BaseDir_Rename_HomeData( const char *from, const char *to, qboolean safe ) {
char *from_ospath, *to_ospath;
if ( !fs_searchpaths ) {
@ -790,11 +811,11 @@ void FS_BaseDir_Rename( const char *from, const char *to, qboolean safe ) {
// don't let sound stutter
S_ClearSoundBuffer();
from_ospath = FS_BaseDir_BuildOSPath( fs_homepath->string, from );
to_ospath = FS_BaseDir_BuildOSPath( fs_homepath->string, to );
from_ospath = FS_BaseDir_BuildOSPath( fs_homedatapath->string, from );
to_ospath = FS_BaseDir_BuildOSPath( fs_homedatapath->string, to );
if ( fs_debug->integer ) {
Com_Printf( "FS_BaseDir_Rename: %s --> %s\n", from_ospath, to_ospath );
Com_Printf( "FS_BaseDir_Rename_HomeData: %s --> %s\n", from_ospath, to_ospath );
}
if ( safe ) {
@ -837,22 +858,41 @@ void FS_FCloseFile( fileHandle_t f ) {
/*
===========
FS_FOpenFileWrite
FS_FOpenFileWrite_HomeConfig
===========
*/
fileHandle_t FS_FOpenFileWrite( const char *filename ) {
fileHandle_t FS_FOpenFileWrite_HomeConfig( const char *filename ) {
return FS_OSPath_FOpenFileWrite(
FS_BuildOSPath(fs_homepath->string, fs_gamedir, filename), filename);
FS_BuildOSPath(fs_homeconfigpath->string, fs_gamedir, filename), filename);
}
/*
===========
FS_FOpenFileAppend
FS_FOpenFileWrite_HomeData
===========
*/
fileHandle_t FS_FOpenFileWrite_HomeData( const char *filename ) {
return FS_OSPath_FOpenFileWrite(
FS_BuildOSPath(fs_homedatapath->string, fs_gamedir, filename), filename);
}
/*
===========
FS_FOpenFileWrite_HomeState
===========
*/
fileHandle_t FS_FOpenFileWrite_HomeState( const char *filename ) {
return FS_OSPath_FOpenFileWrite(
FS_BuildOSPath(fs_homestatepath->string, fs_gamedir, filename), filename);
}
/*
===========
FS_FOpenFileAppend_HomeData
===========
*/
fileHandle_t FS_FOpenFileAppend( const char *filename ) {
fileHandle_t FS_FOpenFileAppend_HomeData( const char *filename ) {
char *ospath;
fileHandle_t f;
@ -868,10 +908,10 @@ fileHandle_t FS_FOpenFileAppend( const char *filename ) {
// don't let sound stutter
S_ClearSoundBuffer();
ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
ospath = FS_BuildOSPath( fs_homedatapath->string, fs_gamedir, filename );
if ( fs_debug->integer ) {
Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
Com_Printf( "FS_FOpenFileAppend_HomeData: %s\n", ospath );
}
FS_CheckFilenameIsMutable( ospath, __func__ );
@ -911,7 +951,7 @@ fileHandle_t FS_FCreateOpenPipeFile( const char *filename ) {
// don't let sound stutter
S_ClearSoundBuffer();
ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
ospath = FS_BuildOSPath( fs_homedatapath->string, fs_gamedir, filename );
if ( fs_debug->integer ) {
Com_Printf( "FS_FCreateOpenPipeFile: %s\n", ospath );
@ -1237,7 +1277,7 @@ long FS_FOpenFileReadDir(const char *filename, searchpath_t *search, fileHandle_
// if you are using FS_ReadFile to find out if a file exists,
// this test can make the search fail although the file is in the directory
// I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
// turned out I used FS_FileExists instead
// turned out I used FS_FileExists_HomeData instead
if(!unpure && fs_numServerPaks)
{
if(!FS_IsExt(filename, ".cfg", len) && // for config files
@ -1909,7 +1949,7 @@ void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
}
f = FS_FOpenFileWrite( qpath );
f = FS_FOpenFileWrite_HomeData( qpath );
if ( !f ) {
Com_Printf( "Failed to open %s\n", qpath );
return;
@ -3132,7 +3172,7 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
// Local name
Q_strcat( neededpaks, len, "@");
// Do we have one with the same name?
if ( FS_BaseDir_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
if ( FS_BaseDir_FileExists_HomeData( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
{
char st[MAX_ZPATH];
// We already have one called this, we need to download it to another name
@ -3159,7 +3199,7 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
Q_strcat( neededpaks, len, ".pk3" );
// Do we have one with the same name?
if ( FS_BaseDir_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
if ( FS_BaseDir_FileExists_HomeData( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
{
Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
}
@ -3302,7 +3342,9 @@ FS_InitPathVars
static void FS_InitPathVars( void ) {
memset( fs_pathVars, 0, sizeof( fs_pathVars ) );
FS_AddPathVar( fs_homepath );
FS_AddPathVar( fs_homeconfigpath );
FS_AddPathVar( fs_homedatapath );
FS_AddPathVar( fs_homestatepath );
FS_AddPathVar( fs_basepath );
FS_AddPathVar( fs_apppath );
FS_AddPathVar( fs_steampath );
@ -3317,7 +3359,18 @@ FS_Startup
*/
static void FS_Startup( const char *gameName )
{
const char *homePath;
cvar_t *fs_homepath = Cvar_Get("fs_homepath", "", CVAR_INIT|CVAR_PROTECTED);
const char *configPath = Sys_DefaultHomeConfigPath();
const char *dataPath = Sys_DefaultHomeDataPath();
const char *statePath = Sys_DefaultHomeStatePath();
if(*(fs_homepath)->string) {
// Setting fs_homepath manually overrides everything else
configPath = dataPath = statePath = fs_homepath->string;
} else if(!*configPath || !*dataPath || !*statePath) {
// #shouldneverhappen; just a sensible fallback
configPath = dataPath = statePath = Sys_DefaultInstallPath();
}
Com_Printf( "----- FS_Startup -----\n" );
@ -3326,11 +3379,9 @@ static void FS_Startup( const char *gameName )
fs_debug = Cvar_Get( "fs_debug", "0", 0 );
fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT|CVAR_PROTECTED );
fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
homePath = Sys_DefaultHomePath();
if (!homePath || !homePath[0]) {
homePath = fs_basepath->string;
}
fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT|CVAR_PROTECTED );
fs_homeconfigpath = Cvar_Get ("fs_homeconfigpath", configPath, CVAR_INIT|CVAR_PROTECTED );
fs_homedatapath = Cvar_Get ("fs_homedatapath", dataPath, CVAR_INIT|CVAR_PROTECTED );
fs_homestatepath = Cvar_Get ("fs_homestatepath", statePath, CVAR_INIT|CVAR_PROTECTED );
fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
fs_steampath = Cvar_Get ("fs_steampath", Sys_SteamPath(), CVAR_INIT|CVAR_PROTECTED );
fs_gogpath = Cvar_Get ("fs_gogpath", Sys_GogPath(), CVAR_INIT|CVAR_PROTECTED );
@ -3363,9 +3414,9 @@ static void FS_Startup( const char *gameName )
Com_Error( ERR_DROP, "Invalid fs_game '%s'", fs_gamedirvar->string );
}
if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string)) {
FS_CreatePath ( fs_homepath->string );
}
FS_CreatePath(fs_homeconfigpath->string);
FS_CreatePath(fs_homedatapath->string);
FS_CreatePath(fs_homestatepath->string);
FS_AddGameDirectories(gameName);
@ -3639,7 +3690,7 @@ static void FS_CheckPak0( void )
#endif
if(installHome)
installPath = fs_homepath->string;
installPath = fs_homedatapath->string;
else
installPath = fs_basepath->string;
@ -4059,6 +4110,9 @@ void FS_InitFilesystem( void ) {
// has already been initialized
Com_StartupVariable("fs_basepath");
Com_StartupVariable("fs_homepath");
Com_StartupVariable("fs_homeconfigpath");
Com_StartupVariable("fs_homedatapath");
Com_StartupVariable("fs_homestatepath");
Com_StartupVariable("fs_game");
if(!FS_FilenameCompare(Cvar_VariableString("fs_game"), com_basegame->string))
@ -4201,7 +4255,7 @@ int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
r = FS_FOpenFileRead( qpath, f, qtrue );
break;
case FS_WRITE:
*f = FS_FOpenFileWrite( qpath );
*f = FS_FOpenFileWrite_HomeData( qpath );
r = 0;
if (*f == 0) {
r = -1;
@ -4210,7 +4264,7 @@ int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
case FS_APPEND_SYNC:
sync = qtrue;
case FS_APPEND:
*f = FS_FOpenFileAppend( qpath );
*f = FS_FOpenFileAppend_HomeData( qpath );
r = 0;
if (*f == 0) {
r = -1;

View File

@ -31,9 +31,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define BASEGAME "foobar"
#define CLIENT_WINDOW_TITLE "changeme"
#define CLIENT_WINDOW_MIN_TITLE "changeme2"
#define HOMEPATH_NAME_UNIX ".foo"
#define HOMEPATH_NAME_WIN "FooBar"
#define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN
#define HOMEPATH_NAME_UNIX_LEGACY ".foo"
#define HOMEPATH_NAME "FooBar"
#define GAMENAME_FOR_MASTER "foobar" // must NOT contain whitespace
#define CINEMATICS_LOGO "foologo.roq"
#define CINEMATICS_INTRO "intro.roq"
@ -45,9 +44,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define BASEGAME "baseq3"
#define CLIENT_WINDOW_TITLE "ioquake3"
#define CLIENT_WINDOW_MIN_TITLE "ioq3"
#define HOMEPATH_NAME_UNIX ".q3a"
#define HOMEPATH_NAME_WIN "Quake3"
#define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN
#define HOMEPATH_NAME_UNIX_LEGACY ".q3a"
#define HOMEPATH_NAME "Quake3"
#define GAMENAME_FOR_MASTER "Quake3Arena"
#define CINEMATICS_LOGO "idlogo.RoQ"
#define CINEMATICS_INTRO "intro.RoQ"

View File

@ -616,7 +616,7 @@ char **FS_ListFiles( const char *directory, const char *extension, int *numfiles
void FS_FreeFileList( char **list );
qboolean FS_FileExists( const char *file );
qboolean FS_FileExists_HomeData( const char *file );
qboolean FS_CreatePath (const char *OSPath);
@ -633,14 +633,18 @@ int FS_GetModList( char *listbuf, int bufsize );
void FS_GetModDescription( const char *modDir, char *description, int descriptionLen );
fileHandle_t FS_FOpenFileWrite( const char *qpath );
fileHandle_t FS_FOpenFileAppend( const char *filename );
fileHandle_t FS_FOpenFileWrite_HomeConfig( const char *filename );
fileHandle_t FS_FOpenFileWrite_HomeData( const char *filename );
fileHandle_t FS_FOpenFileWrite_HomeState( const char *filename );
fileHandle_t FS_FOpenFileAppend_HomeData( const char *filename );
fileHandle_t FS_FCreateOpenPipeFile( const char *filename );
// will properly create any needed paths and deal with seperater character issues
fileHandle_t FS_BaseDir_FOpenFileWrite( const char *filename );
fileHandle_t FS_BaseDir_FOpenFileWrite_HomeConfig( const char *filename );
fileHandle_t FS_BaseDir_FOpenFileWrite_HomeData( const char *filename );
fileHandle_t FS_BaseDir_FOpenFileWrite_HomeState( const char *filename );
long FS_BaseDir_FOpenFileRead( const char *filename, fileHandle_t *fp );
void FS_BaseDir_Rename( const char *from, const char *to, qboolean safe );
void FS_BaseDir_Rename_HomeData( const char *from, const char *to, qboolean safe );
long FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE );
// if uniqueFILE is true, then a new FILE will be fopened even if the file
// is found in an already open pak file. If uniqueFILE is false, you must call
@ -725,7 +729,7 @@ qboolean FS_idPak(char *pak, char *base, int numPaks);
qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring );
void FS_Remove( const char *osPath );
void FS_HomeRemove( const char *homePath );
void FS_Remove_HomeData( const char *homePath );
void FS_FilenameCompletion( const char *dir, const char *ext, char *filter,
qboolean stripExt, void(*callback)(const char *s), qboolean allowNonPureFilesOnDisk );
@ -1122,7 +1126,9 @@ char *Sys_MicrosoftStorePath(void);
char *Sys_DefaultAppPath(void);
#endif
char *Sys_DefaultHomePath(void);
char *Sys_DefaultHomeConfigPath(void);
char *Sys_DefaultHomeDataPath(void);
char *Sys_DefaultHomeStatePath(void);
const char *Sys_Dirname( char *path );
const char *Sys_Basename( char *path );
char *Sys_ConsoleInput(void);

View File

@ -333,7 +333,7 @@ static void R_ModeList_f( void )
NOTE TTimo
some thoughts about the screenshots system:
screenshots get written in fs_homepath + fs_gamedir
screenshots get written in fs_homedatapath + fs_gamedir
vanilla q3 .. baseq3/screenshots/ *.tga
team arena .. missionpack/screenshots/ *.tga

View File

@ -429,7 +429,7 @@ static void R_ModeList_f( void )
NOTE TTimo
some thoughts about the screenshots system:
screenshots get written in fs_homepath + fs_gamedir
screenshots get written in fs_homedatapath + fs_gamedir
vanilla q3 .. baseq3/screenshots/ *.tga
team arena .. missionpack/screenshots/ *.tga

View File

@ -725,7 +725,7 @@ static void SV_WriteBans(void)
Com_sprintf(filepath, sizeof(filepath), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string);
if((writeto = FS_BaseDir_FOpenFileWrite(filepath)))
if((writeto = FS_BaseDir_FOpenFileWrite_HomeState(filepath)))
{
char writebuf[128];
serverBan_t *curban;

View File

@ -181,10 +181,10 @@ Sys_PIDFileName
*/
static char *Sys_PIDFileName( const char *gamedir )
{
const char *homePath = Cvar_VariableString( "fs_homepath" );
const char *homeStatePath = Cvar_VariableString( "fs_homestatepath" );
if( *homePath != '\0' )
return va( "%s/%s/%s", homePath, gamedir, PID_FILENAME );
if( *homeStatePath != '\0' )
return va( "%s/%s/%s", homeStatePath, gamedir, PID_FILENAME );
return NULL;
}

View File

@ -112,73 +112,371 @@ static int Sys_Exec( void )
}
}
#ifdef __APPLE__
/*
==================
Sys_DefaultHomePath
==================
*/
char *Sys_DefaultHomePath(void)
static char *Sys_DefaultHomePath(void)
{
static char homePath[ MAX_OSPATH ] = { 0 };
char *p;
if( !*homePath && com_homepath != NULL )
if( !*homePath )
{
#ifdef __APPLE__
if( ( p = getenv( "HOME" ) ) != NULL )
{
Com_sprintf(homePath, sizeof(homePath), "%s%c", p, PATH_SEP);
Com_sprintf( homePath, sizeof(homePath), "%s%c%s",
p, PATH_SEP, "Library/Application Support/" );
Q_strcat(homePath, sizeof(homePath),
"Library/Application Support/");
if(com_homepath->string[0])
if( com_homepath && com_homepath->string[0] )
Q_strcat(homePath, sizeof(homePath), com_homepath->string);
else
Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_MACOSX);
Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME);
}
#else
if( ( p = getenv( "FLATPAK_ID" ) ) != NULL && *p != '\0' )
{
if( ( p = getenv( "XDG_DATA_HOME" ) ) != NULL && *p != '\0' )
{
Com_sprintf(homePath, sizeof(homePath), "%s%c", p, PATH_SEP);
}
else if( ( p = getenv( "HOME" ) ) != NULL && *p != '\0' )
{
Com_sprintf(homePath, sizeof(homePath), "%s%c.local%cshare%c", p, PATH_SEP, PATH_SEP, PATH_SEP);
}
if( *homePath )
{
char *dir;
if(com_homepath->string[0])
dir = com_homepath->string;
else
dir = HOMEPATH_NAME_UNIX;
if(dir[0] == '.' && dir[1] != '\0')
dir++;
Q_strcat(homePath, sizeof(homePath), dir);
}
}
else if( ( p = getenv( "HOME" ) ) != NULL )
{
Com_sprintf(homePath, sizeof(homePath), "%s%c", p, PATH_SEP);
if(com_homepath->string[0])
Q_strcat(homePath, sizeof(homePath), com_homepath->string);
else
Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_UNIX);
}
#endif
}
return homePath;
}
char *Sys_DefaultHomeConfigPath(void) { return Sys_DefaultHomePath(); }
char *Sys_DefaultHomeDataPath(void) { return Sys_DefaultHomePath(); }
char *Sys_DefaultHomeStatePath(void) { return Sys_DefaultHomePath(); }
#else // __APPLE__
/*
==================
Sys_HomeConfigPath
==================
*/
char *Sys_HomeConfigPath(void)
{
static char homeConfigPath[ MAX_OSPATH ] = { 0 };
char *p;
if( !*homeConfigPath )
{
if( ( p = getenv( "XDG_CONFIG_HOME" ) ) != NULL && *p != '\0' )
Com_sprintf(homeConfigPath, sizeof(homeConfigPath), "%s%c", p, PATH_SEP);
else if( ( p = getenv( "HOME" ) ) != NULL && *p != '\0' )
Com_sprintf(homeConfigPath, sizeof(homeConfigPath), "%s%c.config%c", p, PATH_SEP, PATH_SEP);
if( *homeConfigPath )
{
if( com_homepath && com_homepath->string[0] )
Q_strcat(homeConfigPath, sizeof(homeConfigPath), com_homepath->string);
else
Q_strcat(homeConfigPath, sizeof(homeConfigPath), HOMEPATH_NAME);
}
}
return homeConfigPath;
}
/*
==================
Sys_HomeDataPath
==================
*/
char *Sys_HomeDataPath(void)
{
static char homeDataPath[ MAX_OSPATH ] = { 0 };
char *p;
if( !*homeDataPath )
{
if( ( p = getenv( "XDG_DATA_HOME" ) ) != NULL && *p != '\0' )
Com_sprintf(homeDataPath, sizeof(homeDataPath), "%s%c", p, PATH_SEP);
else if( ( p = getenv( "HOME" ) ) != NULL && *p != '\0' )
Com_sprintf(homeDataPath, sizeof(homeDataPath), "%s%c.local%cshare%c", p, PATH_SEP, PATH_SEP, PATH_SEP);
if( *homeDataPath )
{
if( com_homepath && com_homepath->string[0] )
Q_strcat(homeDataPath, sizeof(homeDataPath), com_homepath->string);
else
Q_strcat(homeDataPath, sizeof(homeDataPath), HOMEPATH_NAME);
}
}
return homeDataPath;
}
/*
==================
Sys_HomeStatePath
==================
*/
char *Sys_HomeStatePath(void)
{
static char homeStatePath[ MAX_OSPATH ] = { 0 };
char *p;
if( !*homeStatePath )
{
if( ( p = getenv( "XDG_STATE_HOME" ) ) != NULL && *p != '\0' )
Com_sprintf(homeStatePath, sizeof(homeStatePath), "%s%c", p, PATH_SEP);
else if( ( p = getenv( "HOME" ) ) != NULL && *p != '\0' )
Com_sprintf(homeStatePath, sizeof(homeStatePath), "%s%c.local%cstate%c", p, PATH_SEP, PATH_SEP, PATH_SEP);
if( *homeStatePath )
{
if( com_homepath && com_homepath->string[0] )
Q_strcat(homeStatePath, sizeof(homeStatePath), com_homepath->string);
else
Q_strcat(homeStatePath, sizeof(homeStatePath), HOMEPATH_NAME);
}
}
return homeStatePath;
}
/*
==================
Sys_LegacyHomePath
==================
*/
static char *Sys_LegacyHomePath(void)
{
static char homePath[ MAX_OSPATH ] = { 0 };
char *p;
if( ( p = getenv( "FLATPAK_ID" ) ) != NULL && *p != '\0' )
{
// Flatpaks always use XDG
return "";
}
if( !*homePath )
{
if( ( p = getenv( "HOME" ) ) != NULL && *p != '\0' )
{
Com_sprintf(homePath, sizeof(homePath), "%s%c%s",
p, PATH_SEP, HOMEPATH_NAME_UNIX_LEGACY);
}
}
return homePath;
}
/*
==================
Sys_MigrateToXDG
==================
*/
qboolean Sys_MigrateToXDG(void)
{
const char *scriptTemplate =
"#!/bin/sh\n"
"set -eu\n"
"legacy_home=\"%s\"\n"
"xdg_config_home=\"%s\"\n"
"xdg_data_home=\"%s\"\n"
"xdg_state_home=\"%s\"\n"
"xdg_config_pattern=\"*.cfg\"\n"
"xdg_data_pattern=\"demos/*.dm_* *.log *.pk3 *.txt \\\n"
" screenshots/*.jpg screenshots/*.tga videos/*.avi\"\n"
"xdg_state_pattern=\"*.dat q3history q3key\"\n"
"glob_copy() {\n"
" game_dir=${1:+$1/}\n"
" dst=\"$2\"\n"
" shift 2\n"
" for pattern in \"$@\"; do\n"
" subdir=$(dirname \"$pattern\")\n"
" [ \"$subdir\" = \".\" ] && subdir=\"\"\n"
" find \"$legacy_home/$game_dir\" \\\n"
" -path \"$legacy_home/$game_dir$pattern\" -type f \\\n"
" -exec mkdir -p \"$dst/$game_dir$subdir\" \\; \\\n"
" -exec cp -av {} \"$dst/$game_dir$subdir\" \\;\n"
" done\n"
"}\n"
"unmatched_copy() {\n"
" game_dir=${1:+$1/}\n"
" shift\n"
" find_args=\"\"\n"
" for pattern in \"$@\"; do\n"
" find_args=\"$find_args \\\n"
" -not -path \\\"$legacy_home/$game_dir$pattern\\\"\"\n"
" done\n"
" eval \"find '$legacy_home/$game_dir' -type f $find_args\" | \\\n"
" while IFS= read -r file; do\n"
" dst=\"$xdg_data_home${file#$legacy_home}\"\n"
" dst_dir=$(dirname \"$dst\")\n"
" mkdir -p \"$dst_dir\"\n"
" cp -av \"$file\" \"$dst\"\n"
" done\n"
"}\n"
"echo \"Starting XDG migration...\"\n"
"glob_copy \"\" \"$xdg_state_home\" \"qkey\"\n"
"for game_dir in \"$legacy_home\"/*; do\n"
" [ -d \"$game_dir\" ] || continue\n"
" game=$(basename \"$game_dir\")\n"
" glob_copy \"$game\" \"$xdg_config_home\" $xdg_config_pattern\n"
" glob_copy \"$game\" \"$xdg_data_home\" $xdg_data_pattern\n"
" glob_copy \"$game\" \"$xdg_state_home\" $xdg_state_pattern\n"
" unmatched_copy \"$game\" \\\n"
" $xdg_config_pattern \\\n"
" $xdg_data_pattern \\\n"
" $xdg_state_pattern\n"
"done\n"
"echo \"XDG migration complete!\"\n";
char scriptBuffer[2048];
int len = Com_sprintf( scriptBuffer, sizeof( scriptBuffer ), scriptTemplate,
Sys_LegacyHomePath( ), Sys_HomeConfigPath( ),
Sys_HomeDataPath( ), Sys_HomeStatePath( ) );
if( len < 0 || len >= (int)sizeof( scriptBuffer ) )
{
Com_Printf( "XDG migration error: substitution failed.\n" );
return qfalse;
}
char scriptPath[] = "/tmp/xdgmigrationXXXXXX";
int fd = mkstemp( scriptPath );
if( fd == -1 )
{
Com_Printf( "XDG migration error: script creation failed.\n" );
return qfalse;
}
if( write( fd, scriptBuffer, len ) != len )
{
close( fd );
unlink( scriptPath );
Com_Printf( "XDG migration error: script write failed.\n" );
return qfalse;
}
close( fd );
if( chmod( scriptPath, 0700 ) == -1 )
{
unlink( scriptPath );
Com_Printf( "XDG migration error: script chmod failed.\n" );
return qfalse;
}
Sys_ClearExecBuffer( );
Sys_AppendToExecBuffer( scriptPath );
int result = Sys_Exec( );
unlink( scriptPath );
return result == 0;
}
/*
==================
Sys_ShouldUseLegacyHomePath
==================
*/
static qboolean Sys_ShouldUseLegacyHomePath(void)
{
if( access( Sys_HomeConfigPath( ), F_OK ) == 0 )
{
// If the XDG config directory exists, prefer XDG layout, regardless
return qfalse;
}
if( ( com_homepath && com_homepath->string[0] ) ||
Cvar_VariableString( "fs_homepath" )[0] )
{
// If a custom homepath has been explicity set then
// that strongly implies that migration isn't desired
return qfalse;
}
const char *legacyHomePath = Sys_LegacyHomePath();
if( !*legacyHomePath || access( legacyHomePath, F_OK ) != 0 )
{
// The legacy home path doesn't exist
return qfalse;
}
char migrationRefusedPath[ MAX_OSPATH ];
Com_sprintf( migrationRefusedPath, sizeof( migrationRefusedPath ),
"%s/.xdgMigrationRefused", legacyHomePath );
// If the user hasn't already refused, ask if they want to migrate
if( access( migrationRefusedPath, F_OK ) != 0 )
{
dialogResult_t result = Sys_Dialog( DT_YES_NO, va(
"Modern games and applications store files in "
"directories according to the Free Desktop standard. "
"Here's what that would look like for %s:\n\n"
"Configuration files:\n %s\n\n"
"Data files; pk3s, screenshots, logs, demos, etc.:\n %s\n\n"
"Internal runtime files:\n %s\n\n"
"At the moment all of these files are found here:\n %s\n\n"
"Do you want to copy your files to these new directories?",
PRODUCT_NAME,
Sys_HomeConfigPath( ), Sys_HomeDataPath( ), Sys_HomeStatePath( ),
legacyHomePath ),
"Home Directory Files Upgrade" );
if( result == DR_YES )
return !Sys_MigrateToXDG( );
// Guard against asking again in future
fclose( fopen( migrationRefusedPath, "w" ) );
}
return qtrue;
}
/*
==================
Sys_DefaultHomeConfigPath
==================
*/
char *Sys_DefaultHomeConfigPath(void)
{
if( Sys_ShouldUseLegacyHomePath( ) )
return Sys_LegacyHomePath( );
return Sys_HomeConfigPath( );
}
/*
==================
Sys_DefaultHomeDataPath
==================
*/
char *Sys_DefaultHomeDataPath(void)
{
if( Sys_ShouldUseLegacyHomePath( ) )
return Sys_LegacyHomePath( );
return Sys_HomeDataPath( );
}
/*
==================
Sys_DefaultHomeStatePath
==================
*/
char *Sys_DefaultHomeStatePath(void)
{
if( Sys_ShouldUseLegacyHomePath( ) )
return Sys_LegacyHomePath( );
return Sys_HomeStatePath( );
}
#endif
/*
================
Sys_SteamPath
@ -668,10 +966,10 @@ void Sys_ErrorDialog( const char *error )
char buffer[ 1024 ];
unsigned int size;
int f = -1;
const char *homepath = Cvar_VariableString( "fs_homepath" );
const char *homedatapath = Cvar_VariableString( "fs_homedatapath" );
const char *gamedir = Cvar_VariableString( "fs_game" );
const char *fileName = "crashlog.txt";
char *ospath = FS_BuildOSPath( homepath, gamedir, fileName );
char *ospath = FS_BuildOSPath( homedatapath, gamedir, fileName );
Sys_Print( va( "%s\n", error ) );
@ -680,7 +978,7 @@ void Sys_ErrorDialog( const char *error )
#endif
// Make sure the write path for the crashlog exists...
if( FS_CreatePath( ospath ) )
if( FS_CreatePath( homedatapath ) )
{
Com_Printf("ERROR: couldn't create path '%s' for crash log.\n", ospath);
return;

View File

@ -88,7 +88,7 @@ void Sys_SetFloatEnv(void)
Sys_DefaultHomePath
================
*/
char *Sys_DefaultHomePath( void )
static char *Sys_DefaultHomePath( void )
{
static char homePath[ MAX_OSPATH ] = { 0 };
@ -108,12 +108,16 @@ char *Sys_DefaultHomePath( void )
if(com_homepath->string[0])
Q_strcat(homePath, sizeof(homePath), com_homepath->string);
else
Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_WIN);
Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME);
}
return homePath;
}
char *Sys_DefaultHomeConfigPath(void) { return Sys_DefaultHomePath(); }
char *Sys_DefaultHomeDataPath(void) { return Sys_DefaultHomePath(); }
char *Sys_DefaultHomeStatePath(void) { return Sys_DefaultHomePath(); }
/*
================
Sys_SteamPath