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 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 # Console
## New cvars ## 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 condition is met, the game will set com_standalone to 1 and is then running
in stand alone mode. in stand alone mode.
If you want the engine to use a different directory in your homepath than If you want the engine to use a different directory in your homepaths than
e.g. "Quake3" on Windows or ".q3a" on Linux, then set a new name at startup "Quake3" then set a new name at startup by adding
by adding
+set com_homepath <homedirname> +set com_homepath <homedirname>
@ -496,7 +520,7 @@ matching game name.
Example line: Example line:
+set com_basegame basefoo +set com_homepath .foo +set com_basegame basefoo +set com_homepath foo
+set com_gamename foo +set com_gamename foo
If you really changed parts that would make vanilla ioquake3 incompatible with 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); botimport.Print(PRT_ERROR, "log file %s is already opened\n", logfile.filename);
return; return;
} //end if } //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"); logfile.fp = fopen(ospath, "wb");
if (!logfile.fp) if (!logfile.fp)
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -74,7 +74,7 @@ LAN_SaveServersToCache
*/ */
void LAN_SaveServersToCache( void ) { void LAN_SaveServersToCache( void ) {
int size; 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.numglobalservers, sizeof(int), fileOut);
FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut); FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut);
size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers); size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers);

View File

@ -205,7 +205,7 @@ void QDECL Com_Printf( const char *fmt, ... ) {
time( &aclock ); time( &aclock );
newtime = localtime( &aclock ); newtime = localtime( &aclock );
logfile = FS_FOpenFileWrite( "qconsole.log" ); logfile = FS_FOpenFileWrite_HomeData( "qconsole.log" );
if(logfile) if(logfile)
{ {
@ -1921,8 +1921,8 @@ void Com_InitJournaling( void ) {
if ( com_journal->integer == 1 ) { if ( com_journal->integer == 1 ) {
Com_Printf( "Journaling events\n"); Com_Printf( "Journaling events\n");
com_journalFile = FS_FOpenFileWrite( "journal.dat" ); com_journalFile = FS_FOpenFileWrite_HomeState( "journal.dat" );
com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" ); com_journalDataFile = FS_FOpenFileWrite_HomeState( "journaldata.dat" );
} else if ( com_journal->integer == 2 ) { } else if ( com_journal->integer == 2 ) {
Com_Printf( "Replaying journaled events\n"); Com_Printf( "Replaying journaled events\n");
FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue ); FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue );
@ -2547,7 +2547,7 @@ static void Com_WriteCDKey( const char *filename, const char *ikey ) {
#ifndef _WIN32 #ifndef _WIN32
savedumask = umask(0077); savedumask = umask(0077);
#endif #endif
f = FS_BaseDir_FOpenFileWrite( fbuffer ); f = FS_BaseDir_FOpenFileWrite_HomeState( fbuffer );
if ( !f ) { if ( !f ) {
Com_Printf ("Couldn't write CD key to %s.\n", fbuffer ); Com_Printf ("Couldn't write CD key to %s.\n", fbuffer );
goto out; goto out;
@ -2921,7 +2921,7 @@ void Com_ReadFromPipe( void )
void Com_WriteConfigToFile( const char *filename ) { void Com_WriteConfigToFile( const char *filename ) {
fileHandle_t f; fileHandle_t f;
f = FS_FOpenFileWrite( filename ); f = FS_FOpenFileWrite_HomeConfig( filename );
if ( !f ) { if ( !f ) {
Com_Printf ("Couldn't write %s.\n", filename ); Com_Printf ("Couldn't write %s.\n", filename );
return; return;
@ -3293,7 +3293,7 @@ void Com_Shutdown (void) {
if( pipefile ) { if( pipefile ) {
FS_FCloseFile( 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 char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
static cvar_t *fs_debug; 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_apppath;
static cvar_t *fs_steampath; 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__ ); FS_CheckFilenameIsMutable( homePath, __func__ );
remove( FS_BuildOSPath( fs_homepath->string, remove( FS_BuildOSPath( fs_homedatapath->string,
fs_gamedir, homePath ) ); 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 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 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 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 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( 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; char *from_ospath, *to_ospath;
if ( !fs_searchpaths ) { if ( !fs_searchpaths ) {
@ -790,11 +811,11 @@ void FS_BaseDir_Rename( const char *from, const char *to, qboolean safe ) {
// don't let sound stutter // don't let sound stutter
S_ClearSoundBuffer(); S_ClearSoundBuffer();
from_ospath = FS_BaseDir_BuildOSPath( fs_homepath->string, from ); from_ospath = FS_BaseDir_BuildOSPath( fs_homedatapath->string, from );
to_ospath = FS_BaseDir_BuildOSPath( fs_homepath->string, to ); to_ospath = FS_BaseDir_BuildOSPath( fs_homedatapath->string, to );
if ( fs_debug->integer ) { 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 ) { 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( 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; char *ospath;
fileHandle_t f; fileHandle_t f;
@ -868,10 +908,10 @@ fileHandle_t FS_FOpenFileAppend( const char *filename ) {
// don't let sound stutter // don't let sound stutter
S_ClearSoundBuffer(); S_ClearSoundBuffer();
ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); ospath = FS_BuildOSPath( fs_homedatapath->string, fs_gamedir, filename );
if ( fs_debug->integer ) { if ( fs_debug->integer ) {
Com_Printf( "FS_FOpenFileAppend: %s\n", ospath ); Com_Printf( "FS_FOpenFileAppend_HomeData: %s\n", ospath );
} }
FS_CheckFilenameIsMutable( ospath, __func__ ); FS_CheckFilenameIsMutable( ospath, __func__ );
@ -911,7 +951,7 @@ fileHandle_t FS_FCreateOpenPipeFile( const char *filename ) {
// don't let sound stutter // don't let sound stutter
S_ClearSoundBuffer(); S_ClearSoundBuffer();
ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); ospath = FS_BuildOSPath( fs_homedatapath->string, fs_gamedir, filename );
if ( fs_debug->integer ) { if ( fs_debug->integer ) {
Com_Printf( "FS_FCreateOpenPipeFile: %s\n", ospath ); 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, // 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 // 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 // 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(!unpure && fs_numServerPaks)
{ {
if(!FS_IsExt(filename, ".cfg", len) && // for config files 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" ); Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
} }
f = FS_FOpenFileWrite( qpath ); f = FS_FOpenFileWrite_HomeData( qpath );
if ( !f ) { if ( !f ) {
Com_Printf( "Failed to open %s\n", qpath ); Com_Printf( "Failed to open %s\n", qpath );
return; return;
@ -3132,7 +3172,7 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
// Local name // Local name
Q_strcat( neededpaks, len, "@"); Q_strcat( neededpaks, len, "@");
// Do we have one with the same name? // 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]; char st[MAX_ZPATH];
// We already have one called this, we need to download it to another name // 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, fs_serverReferencedPakNames[i] );
Q_strcat( neededpaks, len, ".pk3" ); Q_strcat( neededpaks, len, ".pk3" );
// Do we have one with the same name? // 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)"); Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
} }
@ -3302,7 +3342,9 @@ FS_InitPathVars
static void FS_InitPathVars( void ) { static void FS_InitPathVars( void ) {
memset( fs_pathVars, 0, sizeof( fs_pathVars ) ); 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_basepath );
FS_AddPathVar( fs_apppath ); FS_AddPathVar( fs_apppath );
FS_AddPathVar( fs_steampath ); FS_AddPathVar( fs_steampath );
@ -3317,7 +3359,18 @@ FS_Startup
*/ */
static void FS_Startup( const char *gameName ) 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" ); 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_debug = Cvar_Get( "fs_debug", "0", 0 );
fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT|CVAR_PROTECTED ); fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT|CVAR_PROTECTED );
fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT ); fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
homePath = Sys_DefaultHomePath(); fs_homeconfigpath = Cvar_Get ("fs_homeconfigpath", configPath, CVAR_INIT|CVAR_PROTECTED );
if (!homePath || !homePath[0]) { fs_homedatapath = Cvar_Get ("fs_homedatapath", dataPath, CVAR_INIT|CVAR_PROTECTED );
homePath = fs_basepath->string; fs_homestatepath = Cvar_Get ("fs_homestatepath", statePath, CVAR_INIT|CVAR_PROTECTED );
}
fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT|CVAR_PROTECTED );
fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
fs_steampath = Cvar_Get ("fs_steampath", Sys_SteamPath(), CVAR_INIT|CVAR_PROTECTED ); fs_steampath = Cvar_Get ("fs_steampath", Sys_SteamPath(), CVAR_INIT|CVAR_PROTECTED );
fs_gogpath = Cvar_Get ("fs_gogpath", Sys_GogPath(), 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 ); 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_homeconfigpath->string);
FS_CreatePath ( fs_homepath->string ); FS_CreatePath(fs_homedatapath->string);
} FS_CreatePath(fs_homestatepath->string);
FS_AddGameDirectories(gameName); FS_AddGameDirectories(gameName);
@ -3639,7 +3690,7 @@ static void FS_CheckPak0( void )
#endif #endif
if(installHome) if(installHome)
installPath = fs_homepath->string; installPath = fs_homedatapath->string;
else else
installPath = fs_basepath->string; installPath = fs_basepath->string;
@ -4059,6 +4110,9 @@ void FS_InitFilesystem( void ) {
// has already been initialized // has already been initialized
Com_StartupVariable("fs_basepath"); Com_StartupVariable("fs_basepath");
Com_StartupVariable("fs_homepath"); Com_StartupVariable("fs_homepath");
Com_StartupVariable("fs_homeconfigpath");
Com_StartupVariable("fs_homedatapath");
Com_StartupVariable("fs_homestatepath");
Com_StartupVariable("fs_game"); Com_StartupVariable("fs_game");
if(!FS_FilenameCompare(Cvar_VariableString("fs_game"), com_basegame->string)) 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 ); r = FS_FOpenFileRead( qpath, f, qtrue );
break; break;
case FS_WRITE: case FS_WRITE:
*f = FS_FOpenFileWrite( qpath ); *f = FS_FOpenFileWrite_HomeData( qpath );
r = 0; r = 0;
if (*f == 0) { if (*f == 0) {
r = -1; r = -1;
@ -4210,7 +4264,7 @@ int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
case FS_APPEND_SYNC: case FS_APPEND_SYNC:
sync = qtrue; sync = qtrue;
case FS_APPEND: case FS_APPEND:
*f = FS_FOpenFileAppend( qpath ); *f = FS_FOpenFileAppend_HomeData( qpath );
r = 0; r = 0;
if (*f == 0) { if (*f == 0) {
r = -1; r = -1;

View File

@ -31,9 +31,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define BASEGAME "foobar" #define BASEGAME "foobar"
#define CLIENT_WINDOW_TITLE "changeme" #define CLIENT_WINDOW_TITLE "changeme"
#define CLIENT_WINDOW_MIN_TITLE "changeme2" #define CLIENT_WINDOW_MIN_TITLE "changeme2"
#define HOMEPATH_NAME_UNIX ".foo" #define HOMEPATH_NAME_UNIX_LEGACY ".foo"
#define HOMEPATH_NAME_WIN "FooBar" #define HOMEPATH_NAME "FooBar"
#define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN
#define GAMENAME_FOR_MASTER "foobar" // must NOT contain whitespace #define GAMENAME_FOR_MASTER "foobar" // must NOT contain whitespace
#define CINEMATICS_LOGO "foologo.roq" #define CINEMATICS_LOGO "foologo.roq"
#define CINEMATICS_INTRO "intro.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 BASEGAME "baseq3"
#define CLIENT_WINDOW_TITLE "ioquake3" #define CLIENT_WINDOW_TITLE "ioquake3"
#define CLIENT_WINDOW_MIN_TITLE "ioq3" #define CLIENT_WINDOW_MIN_TITLE "ioq3"
#define HOMEPATH_NAME_UNIX ".q3a" #define HOMEPATH_NAME_UNIX_LEGACY ".q3a"
#define HOMEPATH_NAME_WIN "Quake3" #define HOMEPATH_NAME "Quake3"
#define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN
#define GAMENAME_FOR_MASTER "Quake3Arena" #define GAMENAME_FOR_MASTER "Quake3Arena"
#define CINEMATICS_LOGO "idlogo.RoQ" #define CINEMATICS_LOGO "idlogo.RoQ"
#define CINEMATICS_INTRO "intro.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 ); void FS_FreeFileList( char **list );
qboolean FS_FileExists( const char *file ); qboolean FS_FileExists_HomeData( const char *file );
qboolean FS_CreatePath (const char *OSPath); 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 ); void FS_GetModDescription( const char *modDir, char *description, int descriptionLen );
fileHandle_t FS_FOpenFileWrite( const char *qpath ); fileHandle_t FS_FOpenFileWrite_HomeConfig( const char *filename );
fileHandle_t FS_FOpenFileAppend( 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 ); fileHandle_t FS_FCreateOpenPipeFile( const char *filename );
// will properly create any needed paths and deal with seperater character issues // 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 ); 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 ); 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 // 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 // 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 ); qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring );
void FS_Remove( const char *osPath ); 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, void FS_FilenameCompletion( const char *dir, const char *ext, char *filter,
qboolean stripExt, void(*callback)(const char *s), qboolean allowNonPureFilesOnDisk ); qboolean stripExt, void(*callback)(const char *s), qboolean allowNonPureFilesOnDisk );
@ -1122,7 +1126,9 @@ char *Sys_MicrosoftStorePath(void);
char *Sys_DefaultAppPath(void); char *Sys_DefaultAppPath(void);
#endif #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_Dirname( char *path );
const char *Sys_Basename( char *path ); const char *Sys_Basename( char *path );
char *Sys_ConsoleInput(void); char *Sys_ConsoleInput(void);

View File

@ -333,7 +333,7 @@ static void R_ModeList_f( void )
NOTE TTimo NOTE TTimo
some thoughts about the screenshots system: 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 vanilla q3 .. baseq3/screenshots/ *.tga
team arena .. missionpack/screenshots/ *.tga team arena .. missionpack/screenshots/ *.tga

View File

@ -429,7 +429,7 @@ static void R_ModeList_f( void )
NOTE TTimo NOTE TTimo
some thoughts about the screenshots system: 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 vanilla q3 .. baseq3/screenshots/ *.tga
team arena .. missionpack/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); 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]; char writebuf[128];
serverBan_t *curban; serverBan_t *curban;

View File

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

View File

@ -112,73 +112,371 @@ static int Sys_Exec( void )
} }
} }
#ifdef __APPLE__
/* /*
================== ==================
Sys_DefaultHomePath Sys_DefaultHomePath
================== ==================
*/ */
char *Sys_DefaultHomePath(void) static char *Sys_DefaultHomePath(void)
{ {
static char homePath[ MAX_OSPATH ] = { 0 }; static char homePath[ MAX_OSPATH ] = { 0 };
char *p; char *p;
if( !*homePath && com_homepath != NULL ) if( !*homePath )
{ {
#ifdef __APPLE__
if( ( p = getenv( "HOME" ) ) != NULL ) 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), if( com_homepath && com_homepath->string[0] )
"Library/Application Support/");
if(com_homepath->string[0])
Q_strcat(homePath, sizeof(homePath), com_homepath->string); Q_strcat(homePath, sizeof(homePath), com_homepath->string);
else 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; 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 Sys_SteamPath
@ -668,10 +966,10 @@ void Sys_ErrorDialog( const char *error )
char buffer[ 1024 ]; char buffer[ 1024 ];
unsigned int size; unsigned int size;
int f = -1; 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 *gamedir = Cvar_VariableString( "fs_game" );
const char *fileName = "crashlog.txt"; 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 ) ); Sys_Print( va( "%s\n", error ) );
@ -680,7 +978,7 @@ void Sys_ErrorDialog( const char *error )
#endif #endif
// Make sure the write path for the crashlog exists... // 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); Com_Printf("ERROR: couldn't create path '%s' for crash log.\n", ospath);
return; return;

View File

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