# 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:
parent
38737202cb
commit
ebac85ee81
@ -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";
|
||||
|
@ -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));
|
||||
|
@ -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,19 +235,37 @@ 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);
|
||||
debug_assert(len != 0);
|
||||
// strip EXE filename and trailing slash
|
||||
char* slash = strrchr(win_exe_dir, '\\');
|
||||
if(slash)
|
||||
*slash = '\0';
|
||||
else
|
||||
debug_assert(0); // directory name invalid?!
|
||||
// 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, '\\');
|
||||
if(slash)
|
||||
*slash = '\0';
|
||||
else
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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];
|
||||
|
||||
|
||||
//
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,63 +555,154 @@ static size_t ChooseCacheSize()
|
||||
return 96*MiB;
|
||||
}
|
||||
|
||||
fs::path BinariesDir(const CStr& argv0)
|
||||
|
||||
class Paths
|
||||
{
|
||||
// get full path to executable
|
||||
char pathname[PATH_MAX];
|
||||
// .. first try safe, but system-dependent version
|
||||
if(sys_get_executable_name(pathname, PATH_MAX) < 0)
|
||||
public:
|
||||
Paths(const CStr& argv0, const char* subdirectoryName)
|
||||
{
|
||||
// .. failed; use argv[0]
|
||||
errno = 0;
|
||||
if(!realpath(argv0.c_str(), pathname))
|
||||
WARN_ERR(LibError_from_errno(false));
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// make sure it's valid
|
||||
errno = 0;
|
||||
if(access(pathname, X_OK) < 0)
|
||||
WARN_ERR(LibError_from_errno(false));
|
||||
const fs::path& Root() const
|
||||
{
|
||||
return m_root;
|
||||
}
|
||||
|
||||
// strip executable name
|
||||
char* name = (char*)path_name_only(pathname);
|
||||
*name = '\0';
|
||||
const fs::path& RData() const
|
||||
{
|
||||
return m_rdata;
|
||||
}
|
||||
|
||||
return fs::path(pathname);
|
||||
}
|
||||
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];
|
||||
// .. first try safe, but system-dependent version
|
||||
if(sys_get_executable_name(pathname, PATH_MAX) < 0)
|
||||
{
|
||||
// .. failed; use argv[0]
|
||||
errno = 0;
|
||||
if(!realpath(argv0.c_str(), pathname))
|
||||
WARN_ERR(LibError_from_errno(false));
|
||||
}
|
||||
|
||||
// make sure it's valid
|
||||
errno = 0;
|
||||
if(access(pathname, X_OK) < 0)
|
||||
WARN_ERR(LibError_from_errno(false));
|
||||
|
||||
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
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user