#include "testenv.h" #include "launcherpaths.h" #include "localization.h" #include #include #include #include #include #if defined(Q_OS_WIN) #include #include // _wputenv_s #endif namespace { QTemporaryDir *g_sandbox = nullptr; [[noreturn]] void abortSetup(const QString &message) { const QString line = QStringLiteral("TestEnv: ") + message + u'\n'; #if defined(Q_OS_WIN) // В живую консоль пишем UTF-16 напрямую: кодовая страница консоли тогда не важна. const HANDLE handle = GetStdHandle(STD_ERROR_HANDLE); DWORD mode = 0; if (handle != INVALID_HANDLE_VALUE && GetConsoleMode(handle, &mode)) { DWORD written = 0; WriteConsoleW(handle, reinterpret_cast(line.utf16()), DWORD(line.size()), &written, nullptr); std::exit(2); } #endif // Вывод в трубу (ctest, CI) — UTF-8, как его и читает ctest. Локальная 8-битная // кодировка Windows (cp1252/cp1251) превращала кириллицу в «???». std::fputs(line.toUtf8().constData(), stderr); std::fflush(stderr); std::exit(2); } } // namespace void TestEnv::prepareSandbox() { g_sandbox = new QTemporaryDir(QDir::tempPath() + QStringLiteral("/bilauncher-test-XXXXXX")); if (!g_sandbox->isValid()) abortSetup(QStringLiteral("не удалось создать временную папку")); const QString home = QDir::toNativeSeparators(g_sandbox->path()); setEnvPath("HOME", home); setEnvPath("USERPROFILE", home); // LauncherPaths: на Windows QDir::homePath() HOME/USERPROFILE не читает, а эту // переменную лаунчер читает везде. Дочерние процессы получают её по наследству. setEnvPath("BILAUNCHER_HOME", home); // Чужая Java из окружения машины не должна влиять на выбор Java в тестах. qunsetenv("JAVA_HOME"); std::atexit([] { delete g_sandbox; g_sandbox = nullptr; }); } void TestEnv::finishSetup() { QCoreApplication::setApplicationName(QStringLiteral("BiLauncher")); QCoreApplication::setOrganizationName(QStringLiteral("GaleonDev")); const QString root = QDir::cleanPath(LauncherPaths::rootDir()); const QString sandbox = QDir::cleanPath(g_sandbox->path()); const QString game = QDir::cleanPath(LauncherPaths::defaultMinecraftDir()); // Ни одна папка лаунчера не должна указывать на настоящий профиль: resetLauncherRoot() // стирает rootDir() целиком. for (const QString &path : {root, game}) { if (!path.startsWith(sandbox + u'/')) abortSetup(QStringLiteral("путь лаунчера %1 вне песочницы %2 — отказываюсь запускаться") .arg(QDir::toNativeSeparators(path), QDir::toNativeSeparators(sandbox))); } if (!Localization::instance().load()) abortSetup(QStringLiteral("не загрузился каталог переводов")); Localization::instance().setLanguage(QStringLiteral("en")); } void TestEnv::setEnvPath(const char *name, const QString &path) { // На Windows qputenv() пишет в ANSI-кодировке и испортил бы, например, // кириллическое имя пользователя во временном пути. #if defined(Q_OS_WIN) _wputenv_s(reinterpret_cast(QString::fromLatin1(name).utf16()), reinterpret_cast(path.utf16())); #else qputenv(name, QFile::encodeName(path)); #endif } QString TestEnv::homeDir() { return g_sandbox->path(); } QString TestEnv::freshDir(const QString &name) { const QString path = g_sandbox->path() + QStringLiteral("/work/") + name; QDir(path).removeRecursively(); QDir().mkpath(path); return path; } void TestEnv::resetLauncherRoot() { QDir(LauncherPaths::rootDir()).removeRecursively(); }