# hopefully support non-admin accounts and unwritable directories by moving output folders to home/appdata

the old behavior (using directories under binaries/) can be kept by
passing -writableRoot on the command line.
the first game load will be slower than usual due to re-creation of
cached XMBs.

This was SVN commit r7065.
This commit is contained in:
janwas 2009-08-02 11:07:42 +00:00
parent 38737202cb
commit ebac85ee81
12 changed files with 180 additions and 76 deletions

View File

@ -28,8 +28,8 @@
#include "ps/CLogger.h"
#include "ps/Pyrogenesis.h"
static fs::path MOD_PATH(psLogPath()/"../data/mods/_test.mesh");
static fs::path CACHE_PATH(psLogPath()/"../data/_testcache");
static fs::path MOD_PATH(fs::path(psLogDir())/"../data/mods/_test.mesh");
static fs::path CACHE_PATH(fs::path(psLogDir())/"../data/_testcache");
const char* srcDAE = "collada/sphere.dae";
const char* srcPMD = "collada/sphere.pmd";

View File

@ -42,7 +42,7 @@ public:
virtual LibError Mount(const VfsPath& mountPoint, const fs::path& path, size_t flags /* = 0 */, size_t priority /* = 0 */)
{
debug_assert(vfs_path_IsDirectory(mountPoint));
// note: mounting subdirectories is now allowed.
fs::create_directories(path);
VfsDirectory* directory;
CHECK_ERR(vfs_Lookup(mountPoint, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE));

View File

@ -30,6 +30,9 @@
#include "win.h"
#include "winit.h"
#include <shlobj.h> // SHGetFolderPath
WINIT_REGISTER_EARLY_INIT(wutil_Init);
WINIT_REGISTER_LATE_SHUTDOWN(wutil_Shutdown);
@ -232,12 +235,21 @@ bool wutil_HasCommandLineArgument(const char* arg)
char win_sys_dir[MAX_PATH+1];
char win_exe_dir[MAX_PATH+1];
char win_appdata_dir[MAX_PATH+1];
static void GetDirectories()
{
GetSystemDirectory(win_sys_dir, sizeof(win_sys_dir));
WinScopedPreserveLastError s;
const DWORD len = GetModuleFileName(GetModuleHandle(0), win_exe_dir, MAX_PATH);
// system directory
{
const UINT charsWritten = GetSystemDirectory(win_sys_dir, ARRAY_SIZE(win_sys_dir));
debug_assert(charsWritten != 0);
}
// executable's directory
{
const DWORD len = GetModuleFileName(GetModuleHandle(0), win_exe_dir, ARRAY_SIZE(win_exe_dir));
debug_assert(len != 0);
// strip EXE filename and trailing slash
char* slash = strrchr(win_exe_dir, '\\');
@ -247,6 +259,15 @@ static void GetDirectories()
debug_assert(0); // directory name invalid?!
}
// application data
{
HWND hwnd = 0; // ignored unless a dial-up connection is needed to access the folder
HANDLE token = 0;
const HRESULT ret = SHGetFolderPath(hwnd, CSIDL_APPDATA, token, 0, win_appdata_dir);
debug_assert(SUCCEEDED(ret));
}
}
//-----------------------------------------------------------------------------
// user32 fix

View File

@ -99,6 +99,7 @@ public:
WinScopedPreserveLastError()
: m_lastError(GetLastError())
{
SetLastError(0);
}
~WinScopedPreserveLastError()
@ -143,9 +144,10 @@ extern bool wutil_HasCommandLineArgument(const char* arg);
// directories
//
// neither of these end in a slash.
// none of these end with a slash.
extern char win_sys_dir[MAX_PATH+1];
extern char win_exe_dir[MAX_PATH+1];
extern char win_appdata_dir[MAX_PATH+1];
//

View File

@ -258,7 +258,7 @@ CNetLogFileSink::CNetLogFileSink( void )
CNetLogger::GetStringTime( time );
// Make relative path
fs::path path(psLogPath()/"net_log");
fs::path path(fs::path(psLogDir())/"net_log");
path /= time+".txt";
m_FileName = path.external_file_string();
m_Append = true;

View File

@ -54,10 +54,10 @@ const char* html_footer = "";
CLogger::CLogger()
{
fs::path mainlogPath(psLogPath()/"mainlog.html");
fs::path mainlogPath(fs::path(psLogDir())/"mainlog.html");
m_MainLog = new std::ofstream(mainlogPath.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc);
fs::path interestinglogPath(psLogPath()/"interestinglog.html");
fs::path interestinglogPath(fs::path(psLogDir())/"interestinglog.html");
m_InterestingLog = new std::ofstream(interestinglogPath.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc);
m_OwnsStreams = true;

View File

@ -274,7 +274,7 @@ bool CConfigDB::Reload(EConfigNamespace ns)
shared_ptr<u8> buffer; size_t buflen;
{
// Handle missing files quietly
if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) == ERR::VFS_FILE_NOT_FOUND)
if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0)
{
LOG(CLogger::Warning, LOG_CATEGORY, "Cannot find config file \"%s\" - ignoring", m_ConfigFile[ns].c_str());
return false;

View File

@ -17,6 +17,9 @@
#include "precompiled.h"
#if OS_WIN
#include "lib/sysdep/os/win/wutil.h"
#endif
#include "lib/external_libraries/sdl.h"
#include "lib/ogl.h"
#include "lib/timer.h"
@ -88,6 +91,7 @@
#include "network/NetServer.h"
#include "network/NetClient.h"
#include "ps/Pyrogenesis.h" // psSetLogDir
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/GameSetup/Config.h"
@ -551,7 +555,75 @@ static size_t ChooseCacheSize()
return 96*MiB;
}
fs::path BinariesDir(const CStr& argv0)
class Paths
{
public:
Paths(const CStr& argv0, const char* subdirectoryName)
{
m_root = Root(argv0);
m_rdata = m_root/"data";
// everything is a subdirectory of the root
if(!subdirectoryName)
{
m_data = m_rdata;
m_config = m_data/"config";
m_cache = m_data/"cache";
m_logs = m_root/"logs";
}
else
{
#if OS_WIN
const fs::path appdata(fs::path(win_appdata_dir)/subdirectoryName);
m_data = appdata/"data";
m_config = appdata/"config";
m_cache = appdata/"cache";
m_logs = appdata/"logs";
#else
const char* envHome = getenv("HOME");
debug_assert(envHome);
const fs::path home(envHome);
m_data = XDG_Path("XDG_DATA_HOME", home/".local/share")/subdirectoryName;
m_config = XDG_Path("XDG_CONFIG_HOME", home/".config")/subdirectoryName;
m_cache = XDG_Path("XDG_CACHE_HOME", home/".cache")/subdirectoryName;
m_logs = m_config/"logs";
#endif
}
}
const fs::path& Root() const
{
return m_root;
}
const fs::path& RData() const
{
return m_rdata;
}
const fs::path& Data() const
{
return m_data;
}
const fs::path& Config() const
{
return m_config;
}
const fs::path& Cache() const
{
return m_cache;
}
const fs::path& Logs() const
{
return m_logs;
}
private:
static fs::path Root(const CStr& argv0)
{
// get full path to executable
char pathname[PATH_MAX];
@ -569,45 +641,68 @@ fs::path BinariesDir(const CStr& argv0)
if(access(pathname, X_OK) < 0)
WARN_ERR(LibError_from_errno(false));
// strip executable name
char* name = (char*)path_name_only(pathname);
*name = '\0';
return fs::path(pathname);
fs::path path(pathname);
for(size_t i = 0; i < 3; i++) // remove "system/name.exe"
path.remove_leaf();
return path;
}
static fs::path XDG_Path(const char* envname, const fs::path& home, const fs::path& defaultPath)
{
const char* path = getenv(envname);
if(path)
{
if(path[0] != '/') // relative to $HOME
return home/path;
return fs::path(path);
}
return defaultPath;
}
// read-only directories, fixed paths relative to executable
fs::path m_root;
fs::path m_rdata;
// writable directories
fs::path m_data;
fs::path m_config;
fs::path m_cache;
fs::path m_logs; // special-cased in single-root-folder installations
};
static void InitVfs(const CmdLineArgs& args)
{
TIMER("InitVfs");
const char* subdirectory = args.Has("writableRoot")? 0 : "0ad";
const Paths paths(args.GetArg0(), subdirectory);
fs::path logs(paths.Logs());
fs::create_directories(logs);
psSetLogDir(logs.string().c_str());
const size_t cacheSize = ChooseCacheSize();
g_VFS = CreateVfs(cacheSize);
const fs::path binariesDir(BinariesDir(args.GetArg0()));
g_VFS->Mount("screenshots/", binariesDir/"../data/screenshots");
g_VFS->Mount("config/", binariesDir/"../data/config");
g_VFS->Mount("profiles/", binariesDir/"../data/profiles");
// rationale:
// - this is in a separate real directory so that it can later be moved
// to $APPDATA to allow running without Admin access.
// - we mount as archivable so that all files will be added to archive.
// even though we write out XMBs here, they will eventually be read,
// so putting them in an archive boosts performance.
g_VFS->Mount("cache/", binariesDir/"../data/cache", VFS_MOUNT_ARCHIVABLE);
g_VFS->Mount("screenshots/", paths.Data()/"screenshots");
g_VFS->Mount("config/", paths.RData()/"config");
g_VFS->Mount("profiles/", paths.Config()/"profiles");
g_VFS->Mount("cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads)
std::vector<CStr> mods = args.GetMultiple("mod");
mods.push_back("public");
if(!args.Has("onlyPublicFiles"))
mods.push_back("internal");
fs::path modArchivePath(paths.Cache()/"mods");
fs::path modLoosePath(paths.RData()/"mods");
for (size_t i = 0; i < mods.size(); ++i)
{
CStr path = "mods/" + mods[i];
size_t priority = i;
const int flags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE;
g_VFS->Mount("", (binariesDir/"../data")/path, flags, priority);
g_VFS->Mount("", modLoosePath/mods[i], flags, priority);
g_VFS->Mount("", modArchivePath/mods[i], flags, priority);
}
// don't try g_VFS->Display yet: SDL_Init hasn't yet redirected stdout
@ -916,7 +1011,7 @@ void Init(const CmdLineArgs& args, int flags)
hooks.translate = psTranslate;
hooks.translate_free = psTranslateFree;
hooks.bundle_logs = psBundleLogs;
hooks.get_log_dir = psGetLogDir;
hooks.get_log_dir = psLogDir;
app_hooks_update(&hooks);
// Set up the console early, so that debugging

View File

@ -428,7 +428,7 @@ void CProfileViewer::SaveToFile()
{
// Open the file. (It will be closed when the CProfileViewer
// destructor is called.)
fs::path path(psLogPath()/"profile.txt");
fs::path path(fs::path(psLogDir())/"profile.txt");
m->outputStream.open(path.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc);
if (m->outputStream.fail())

View File

@ -93,35 +93,25 @@ void psBundleLogs(FILE* f)
fwprintf(f, L"SVN Revision: %s\n\n", svn_revision);
fwprintf(f, L"System info:\n\n");
fs::path path1(psLogPath()/"system_info.txt");
fs::path path1(fs::path(psLogDir())/"system_info.txt");
AppendAsciiFile(f, path1.external_file_string().c_str());
fwprintf(f, L"\n\n====================================\n\n");
fwprintf(f, L"Main log:\n\n");
fs::path path2(psLogPath()/"mainlog.html");
fs::path path2(fs::path(psLogDir())/"mainlog.html");
AppendAsciiFile(f, path2.external_file_string().c_str());
fwprintf(f, L"\n\n====================================\n\n");
}
const char* psGetLogDir()
static char logDir[PATH_MAX];
void psSetLogDir(const char* path)
{
static char N_log_dir[PATH_MAX];
ONCE(\
char N_exe_name[PATH_MAX];\
(void)sys_get_executable_name(N_exe_name, ARRAY_SIZE(N_exe_name));\
/* strip app name (we only want its path) */\
path_strip_fn(N_exe_name);\
(void)path_append(N_log_dir, N_exe_name, "../logs/");
);
return N_log_dir;
path_copy(logDir, path);
}
fs::path psLogPath()
const char* psLogDir()
{
char exePathname[PATH_MAX];
(void)sys_get_executable_name(exePathname, ARRAY_SIZE(exePathname));
path_strip_fn(exePathname);
return fs::path(exePathname)/"../logs/";
return logDir;
}

View File

@ -42,11 +42,7 @@ extern const wchar_t* psTranslate(const wchar_t* text);
extern void psTranslateFree(const wchar_t* text);
extern void psBundleLogs(FILE* f);
// (this is used by AppHooks during crash reporting, where it's useful
// not to allocate any memory.)
extern const char* psGetLogDir();
// same as psGetLogDir, but more convenient (yet doesn't cache the results).
extern fs::path psLogPath();
extern void psSetLogDir(const char* path); // set during InitVfs
extern const char* psLogDir(); // used by AppHooks and engine code when reporting errors
#endif

View File

@ -71,7 +71,7 @@ void WriteSystemInfo()
struct utsname un;
uname(&un);
fs::path pathname(psLogPath()/"system_info.txt");
fs::path pathname(fs::path(psLogDir())/"system_info.txt");
FILE* f = fopen(pathname.external_file_string().c_str(), "w");
if(!f)
return;