mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-22 14:24:33 +01:00
Refactor Steam handling, allow downloading game files on Linux (#25643)
* Refactor Steam path handling * Move RCT2 Steam path retrieval to its own function * Create function to trigger Steam download * Create CLI command to trigger Steam download * Fix inclusion of "thirdparty" openrct2-cli for Android
This commit is contained in:
@@ -3843,3 +3843,6 @@ STR_7010 :Could not start replay, file ‘{STRING}’ doesn’t exist or isn
|
||||
STR_7011 :Could not start replay
|
||||
STR_7012 :Polish Złoty (PLN)
|
||||
STR_7013 :Drag areas of path
|
||||
STR_7014 :I own the game on Steam, but I haven’t installed it yet.
|
||||
STR_7015 :Please close Steam if it’s running, then click ‘OK’.
|
||||
STR_7016 :OpenRCT2 has tried to trigger a download in Steam. Please open Steam and let it download the game. When Steam is finished, click ‘OK’.
|
||||
|
||||
@@ -272,6 +272,7 @@ target_include_directories(openrct2-ui PRIVATE "${ORCT2_ROOT}/src/thirdparty/duk
|
||||
target_include_directories(openrct2-ui PRIVATE "${ORCT2_ROOT}/src")
|
||||
target_include_directories(openrct2-ui SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty")
|
||||
target_include_directories(openrct2-cli PRIVATE "${ORCT2_ROOT}/src")
|
||||
target_include_directories(openrct2-cli SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty")
|
||||
|
||||
target_include_directories(openrct2 PRIVATE "/opt/openrct2/include/nlohmann/../")
|
||||
target_include_directories(openrct2-ui PRIVATE "/opt/openrct2/include/nlohmann/../")
|
||||
|
||||
@@ -125,5 +125,6 @@ namespace OpenRCT2
|
||||
exitcode_t HandleCommandDefault();
|
||||
|
||||
exitcode_t HandleCommandUri(CommandLineArgEnumerator* enumerator);
|
||||
exitcode_t HandleCommandTriggerSteamDownload(CommandLineArgEnumerator* enumerator);
|
||||
} // namespace CommandLine
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -135,6 +135,7 @@ namespace OpenRCT2
|
||||
DefineCommand("set-rct2", "<path>", kStandardOptions, HandleCommandSetRCT2),
|
||||
DefineCommand("scan-objects", "<path>", kStandardOptions, HandleCommandScanObjects),
|
||||
DefineCommand("handle-uri", "openrct2://.../", kStandardOptions, CommandLine::HandleCommandUri),
|
||||
DefineCommand("trigger-steam-download", "", kStandardOptions, CommandLine::HandleCommandTriggerSteamDownload),
|
||||
|
||||
#if defined(_WIN32)
|
||||
DefineCommand("register-shell", "", RegisterShellOptions, HandleCommandRegisterShell),
|
||||
@@ -496,4 +497,14 @@ namespace OpenRCT2
|
||||
|
||||
// TODO Print other potential information (e.g. user, hardware)
|
||||
}
|
||||
|
||||
exitcode_t CommandLine::HandleCommandTriggerSteamDownload([[maybe_unused]] CommandLineArgEnumerator* enumerator)
|
||||
{
|
||||
if (!Platform::triggerSteamDownload())
|
||||
{
|
||||
return EXITCODE_FAIL;
|
||||
}
|
||||
|
||||
return EXITCODE_OK;
|
||||
}
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -716,13 +716,24 @@ namespace OpenRCT2::Config
|
||||
}
|
||||
}
|
||||
|
||||
auto steamPath = Platform::GetSteamPath();
|
||||
if (!steamPath.empty())
|
||||
auto steamPaths = Platform::GetSteamPaths();
|
||||
if (steamPaths.isSteamPresent())
|
||||
{
|
||||
std::string location = Path::Combine(steamPath, Platform::GetRCT1SteamDir());
|
||||
if (RCT1DataPresentAtLocation(location))
|
||||
for (const auto& root : steamPaths.roots)
|
||||
{
|
||||
return location;
|
||||
auto nativePath = Path::Combine(root, steamPaths.nativeFolder, Platform::kSteamRCT1Data.nativeFolder);
|
||||
if (RCT1DataPresentAtLocation(nativePath))
|
||||
{
|
||||
return nativePath;
|
||||
}
|
||||
if (!steamPaths.downloadDepotFolder.empty())
|
||||
{
|
||||
auto downloadDepotPath = steamPaths.getDownloadDepotFolder(root, Platform::kSteamRCT1Data);
|
||||
if (RCT1DataPresentAtLocation(downloadDepotPath))
|
||||
{
|
||||
return downloadDepotPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,6 +745,39 @@ namespace OpenRCT2::Config
|
||||
return {};
|
||||
}
|
||||
|
||||
static u8string FindRCT2SteamPath()
|
||||
{
|
||||
auto steamPaths = Platform::GetSteamPaths();
|
||||
if (steamPaths.isSteamPresent())
|
||||
{
|
||||
const std::array<Platform::SteamGameData, 2> gamesToCheck = {
|
||||
Platform::kSteamRCT2Data,
|
||||
Platform::kSteamRCTCData,
|
||||
};
|
||||
for (const auto& root : steamPaths.roots)
|
||||
{
|
||||
for (const auto& game : gamesToCheck)
|
||||
{
|
||||
auto nativePath = Path::Combine(root, steamPaths.nativeFolder, game.nativeFolder);
|
||||
if (Platform::OriginalGameDataExists(nativePath))
|
||||
{
|
||||
return nativePath;
|
||||
}
|
||||
if (!steamPaths.downloadDepotFolder.empty())
|
||||
{
|
||||
auto downloadDepotPath = steamPaths.getDownloadDepotFolder(root, game);
|
||||
if (Platform::OriginalGameDataExists(downloadDepotPath))
|
||||
{
|
||||
return downloadDepotPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the RCT2 installation directory.
|
||||
* This should be created from some other resource when OpenRCT2 grows.
|
||||
@@ -753,20 +797,11 @@ namespace OpenRCT2::Config
|
||||
}
|
||||
}
|
||||
|
||||
auto steamPath = Platform::GetSteamPath();
|
||||
// Will only return a path if the RCT2 data is present, so no need to check twice.
|
||||
auto steamPath = FindRCT2SteamPath();
|
||||
if (!steamPath.empty())
|
||||
{
|
||||
std::string location = Path::Combine(steamPath, Platform::GetRCT2SteamDir());
|
||||
if (Platform::OriginalGameDataExists(location))
|
||||
{
|
||||
return location;
|
||||
}
|
||||
|
||||
std::string location2 = Path::Combine(steamPath, Platform::GetRCTClassicSteamDir());
|
||||
if (Platform::OriginalGameDataExists(location2))
|
||||
{
|
||||
return location2;
|
||||
}
|
||||
return steamPath;
|
||||
}
|
||||
|
||||
auto discordPath = Platform::GetFolderPath(SpecialFolder::rct2Discord);
|
||||
@@ -874,6 +909,7 @@ namespace OpenRCT2::Config
|
||||
{
|
||||
uiContext.ShowMessageBox(LanguageGetString(STR_NEEDS_RCT2_FILES));
|
||||
std::string gog = LanguageGetString(STR_OWN_ON_GOG);
|
||||
std::string steam = LanguageGetString(STR_OWN_ON_STEAM);
|
||||
std::string hdd = LanguageGetString(STR_INSTALLED_ON_HDD);
|
||||
|
||||
std::vector<std::string> options;
|
||||
@@ -883,6 +919,7 @@ namespace OpenRCT2::Config
|
||||
{
|
||||
options.push_back(hdd);
|
||||
options.push_back(gog);
|
||||
options.push_back(steam);
|
||||
int optionIndex = uiContext.ShowMenuDialog(
|
||||
options, LanguageGetString(STR_OPENRCT2_SETUP), LanguageGetString(STR_WHICH_APPLIES_BEST));
|
||||
if (optionIndex < 0 || static_cast<uint32_t>(optionIndex) >= options.size())
|
||||
@@ -940,6 +977,21 @@ namespace OpenRCT2::Config
|
||||
possibleInstallPaths.emplace_back(dest);
|
||||
possibleInstallPaths.emplace_back(Path::Combine(dest, u8"app"));
|
||||
}
|
||||
else if (chosenOption == steam)
|
||||
{
|
||||
uiContext.ShowMessageBox(LanguageGetString(STR_PLEASE_CLOSE_STEAM));
|
||||
|
||||
Platform::triggerSteamDownload();
|
||||
|
||||
uiContext.ShowMessageBox(LanguageGetString(STR_WAIT_FOR_STEAM_DOWNLOAD));
|
||||
|
||||
auto steamPath = FindRCT2SteamPath();
|
||||
if (!steamPath.empty())
|
||||
{
|
||||
Get().general.rct2Path = steamPath;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (possibleInstallPaths.empty())
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -1602,6 +1602,9 @@ enum : StringId
|
||||
STR_THIS_WILL_TAKE_A_FEW_MINUTES = 6407,
|
||||
STR_INSTALL_INNOEXTRACT = 6408,
|
||||
STR_NOT_THE_GOG_INSTALLER = 6409,
|
||||
STR_OWN_ON_STEAM = 7014,
|
||||
STR_PLEASE_CLOSE_STEAM = 7015,
|
||||
STR_WAIT_FOR_STEAM_DOWNLOAD = 7016,
|
||||
|
||||
STR_TILE_INSPECTOR_TOGGLE_INVISIBILITY_TIP = 6436,
|
||||
|
||||
|
||||
@@ -148,22 +148,7 @@ namespace OpenRCT2::Platform
|
||||
return isImperial == JNI_TRUE ? MeasurementFormat::Imperial : MeasurementFormat::Metric;
|
||||
}
|
||||
|
||||
std::string GetSteamPath()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
u8string GetRCT1SteamDir()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
u8string GetRCT2SteamDir()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
u8string GetRCTClassicSteamDir()
|
||||
SteamPaths GetSteamPaths()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -213,4 +213,43 @@ namespace OpenRCT2::Platform
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SteamPaths::isSteamPresent() const
|
||||
{
|
||||
return !roots.empty();
|
||||
}
|
||||
|
||||
u8string SteamPaths::getDownloadDepotFolder(u8string_view steamroot, const SteamGameData& data) const
|
||||
{
|
||||
return Path::Combine(
|
||||
steamroot, downloadDepotFolder, "app_" + std::to_string(data.appId), "depot_" + std::to_string(data.depotId));
|
||||
}
|
||||
|
||||
bool triggerSteamDownload()
|
||||
{
|
||||
const auto steamPaths = GetSteamPaths();
|
||||
if (!steamPaths.isSteamPresent() || steamPaths.manifests.empty())
|
||||
return false;
|
||||
|
||||
const auto manifestsDir = Path::Combine(steamPaths.roots[0], steamPaths.manifests);
|
||||
const std::array<SteamGameData, 3> gamesToTrigger = { kSteamRCT2Data, kSteamRCTCData, kSteamRCT1Data };
|
||||
for (const auto& game : gamesToTrigger)
|
||||
{
|
||||
auto fullFilename = Path::Combine(manifestsDir, "appmanifest_" + std::to_string(game.appId) + ".acf");
|
||||
// If the file exists, we assume a download has been triggered already.
|
||||
if (File::Exists(fullFilename))
|
||||
continue;
|
||||
|
||||
// clang-format off
|
||||
auto buffer = u8string("\"AppState\"\r\n") + u8string("{\r\n")
|
||||
+ u8string(" \"AppID\" \"" + std::to_string(game.appId) + "\"\r\n")
|
||||
+ u8string(" \"Universe\" \"1\"\r\n")
|
||||
+ u8string(" \"installdir\" \"" + game.nativeFolder + "\"\r\n")
|
||||
+ u8string(" \"StateFlags\" \"1026\"\r\n") + u8string("}\r\n");
|
||||
// clang-format on
|
||||
File::WriteAllBytes(fullFilename, buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace OpenRCT2::Platform
|
||||
|
||||
@@ -97,22 +97,7 @@ namespace OpenRCT2::Platform
|
||||
return isImperial == 1 ? MeasurementFormat::Imperial : MeasurementFormat::Metric;
|
||||
}
|
||||
|
||||
std::string GetSteamPath()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
u8string GetRCT1SteamDir()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
u8string GetRCT2SteamDir()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
u8string GetRCTClassicSteamDir()
|
||||
SteamPaths GetSteamPaths()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -320,65 +320,52 @@ namespace OpenRCT2::Platform
|
||||
return MeasurementFormat::Metric;
|
||||
}
|
||||
|
||||
std::string GetSteamPath()
|
||||
SteamPaths GetSteamPaths()
|
||||
{
|
||||
SteamPaths ret = {};
|
||||
ret.nativeFolder = "steamapps/common";
|
||||
ret.downloadDepotFolder = "ubuntu12_32/steamapps/content";
|
||||
ret.manifests = "steamapps";
|
||||
|
||||
const char* steamRoot = getenv("STEAMROOT");
|
||||
if (steamRoot != nullptr)
|
||||
{
|
||||
return Path::Combine(steamRoot, u8"ubuntu12_32/steamapps/content");
|
||||
ret.roots.emplace_back(steamRoot);
|
||||
}
|
||||
|
||||
const char* localSharePath = getenv("XDG_DATA_HOME");
|
||||
if (localSharePath != nullptr)
|
||||
{
|
||||
auto steamPath = Path::Combine(localSharePath, u8"Steam/ubuntu12_32/steamapps/content");
|
||||
if (Path::DirectoryExists(steamPath))
|
||||
auto xdgDataHomeSteamPath = Path::Combine(localSharePath, u8"Steam");
|
||||
if (Path::DirectoryExists(xdgDataHomeSteamPath))
|
||||
{
|
||||
return steamPath;
|
||||
ret.roots.emplace_back(xdgDataHomeSteamPath);
|
||||
}
|
||||
}
|
||||
|
||||
const char* homeDir = getpwuid(getuid())->pw_dir;
|
||||
if (homeDir == nullptr)
|
||||
if (homeDir != nullptr)
|
||||
{
|
||||
return {};
|
||||
auto localShareSteamPath = Path::Combine(homeDir, u8".local/share/Steam");
|
||||
if (Path::DirectoryExists(localShareSteamPath))
|
||||
{
|
||||
ret.roots.emplace_back(localShareSteamPath);
|
||||
}
|
||||
|
||||
auto oldSteamPath = Path::Combine(homeDir, u8".steam/steam");
|
||||
if (Path::DirectoryExists(oldSteamPath))
|
||||
{
|
||||
ret.roots.emplace_back(oldSteamPath);
|
||||
}
|
||||
|
||||
auto snapLocalShareSteamPath = Path::Combine(homeDir, u8"snap/steam/common/.local/share/Steam");
|
||||
if (Path::DirectoryExists(snapLocalShareSteamPath))
|
||||
{
|
||||
ret.roots.emplace_back(snapLocalShareSteamPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer new path for Steam, which is the default when using with Proton
|
||||
auto steamPath = Path::Combine(homeDir, u8".local/share/Steam/steamapps/common");
|
||||
if (Path::DirectoryExists(steamPath))
|
||||
{
|
||||
return steamPath;
|
||||
}
|
||||
|
||||
// Fallback paths
|
||||
steamPath = Path::Combine(homeDir, u8".local/share/Steam/ubuntu12_32/steamapps/content");
|
||||
if (Path::DirectoryExists(steamPath))
|
||||
{
|
||||
return steamPath;
|
||||
}
|
||||
|
||||
steamPath = Path::Combine(homeDir, u8".steam/steam/ubuntu12_32/steamapps/content");
|
||||
if (Path::DirectoryExists(steamPath))
|
||||
{
|
||||
return steamPath;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
u8string GetRCT1SteamDir()
|
||||
{
|
||||
return u8"Rollercoaster Tycoon Deluxe";
|
||||
}
|
||||
|
||||
u8string GetRCT2SteamDir()
|
||||
{
|
||||
return u8"Rollercoaster Tycoon 2";
|
||||
}
|
||||
|
||||
u8string GetRCTClassicSteamDir()
|
||||
{
|
||||
return u8"RollerCoaster Tycoon Classic";
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> GetSearchablePathsRCT1()
|
||||
|
||||
@@ -737,7 +737,7 @@ namespace OpenRCT2::Platform
|
||||
return isElevated;
|
||||
}
|
||||
|
||||
std::string GetSteamPath()
|
||||
SteamPaths GetSteamPaths()
|
||||
{
|
||||
wchar_t* wSteamPath;
|
||||
HKEY hKey;
|
||||
@@ -759,12 +759,18 @@ namespace OpenRCT2::Platform
|
||||
result = RegQueryValueExW(hKey, L"SteamPath", nullptr, &type, reinterpret_cast<LPBYTE>(wSteamPath), &size);
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
auto utf8SteamPath = String::toUtf8(wSteamPath);
|
||||
outPath = Path::Combine(utf8SteamPath, u8"steamapps", u8"common");
|
||||
outPath = String::toUtf8(wSteamPath);
|
||||
}
|
||||
free(wSteamPath);
|
||||
RegCloseKey(hKey);
|
||||
return outPath;
|
||||
|
||||
SteamPaths ret = {};
|
||||
ret.roots.emplace_back(outPath);
|
||||
ret.nativeFolder = "steamapps/common";
|
||||
ret.downloadDepotFolder = "steamapps/content";
|
||||
ret.manifests = "steamapps";
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string GetFontPath(const TTFFontDescriptor& font)
|
||||
@@ -796,21 +802,6 @@ namespace OpenRCT2::Platform
|
||||
return GetLogicalDrives();
|
||||
}
|
||||
|
||||
u8string GetRCT1SteamDir()
|
||||
{
|
||||
return u8"Rollercoaster Tycoon Deluxe";
|
||||
}
|
||||
|
||||
u8string GetRCT2SteamDir()
|
||||
{
|
||||
return u8"Rollercoaster Tycoon 2";
|
||||
}
|
||||
|
||||
u8string GetRCTClassicSteamDir()
|
||||
{
|
||||
return u8"RollerCoaster Tycoon Classic";
|
||||
}
|
||||
|
||||
time_t FileGetModifiedTime(u8string_view path)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA data{};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <bit>
|
||||
#include <ctime>
|
||||
#include <optional>
|
||||
#include <sfl/static_vector.hpp>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -60,6 +61,49 @@ struct TTFFontDescriptor;
|
||||
|
||||
namespace OpenRCT2::Platform
|
||||
{
|
||||
struct SteamGameData
|
||||
{
|
||||
u8string nativeFolder;
|
||||
uint32_t appId;
|
||||
uint32_t depotId;
|
||||
};
|
||||
const SteamGameData kSteamRCT1Data = {
|
||||
.nativeFolder = u8"Rollercoaster Tycoon Deluxe",
|
||||
.appId = 285310,
|
||||
.depotId = 285311,
|
||||
};
|
||||
const SteamGameData kSteamRCT2Data = {
|
||||
.nativeFolder = u8"Rollercoaster Tycoon 2",
|
||||
.appId = 285330,
|
||||
.depotId = 285331,
|
||||
};
|
||||
const SteamGameData kSteamRCTCData = {
|
||||
.nativeFolder = u8"RollerCoaster Tycoon Classic",
|
||||
.appId = 683900,
|
||||
.depotId = 683901,
|
||||
};
|
||||
|
||||
struct SteamPaths
|
||||
{
|
||||
sfl::static_vector<u8string, 5> roots{};
|
||||
/**
|
||||
* Used by native applications and applications installed through Steam Play.
|
||||
*/
|
||||
u8string nativeFolder{};
|
||||
/**
|
||||
* Used by applications downloaded through download_depot. Most likely used on macOS and Linux,
|
||||
* though technically possible on Windows too.
|
||||
*/
|
||||
u8string downloadDepotFolder{};
|
||||
/**
|
||||
* Directory that contains the manifests to trigger a download.
|
||||
*/
|
||||
u8string manifests{};
|
||||
|
||||
bool isSteamPresent() const;
|
||||
u8string getDownloadDepotFolder(u8string_view steamroot, const SteamGameData& data) const;
|
||||
};
|
||||
|
||||
constexpr u8string_view kRCTClassicWindowsDataFolder = u8"Assets";
|
||||
// clang-format off
|
||||
constexpr u8string_view kRCTClassicMacOSDataFolder =
|
||||
@@ -101,7 +145,8 @@ namespace OpenRCT2::Platform
|
||||
|
||||
std::string GetUsername();
|
||||
|
||||
std::string GetSteamPath();
|
||||
SteamPaths GetSteamPaths();
|
||||
bool triggerSteamDownload();
|
||||
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__) || defined(__NetBSD__)
|
||||
std::string GetEnvironmentPath(const char* name);
|
||||
std::string GetHomePath();
|
||||
@@ -138,9 +183,6 @@ namespace OpenRCT2::Platform
|
||||
|
||||
bool LockSingleInstance();
|
||||
|
||||
u8string GetRCT1SteamDir();
|
||||
u8string GetRCT2SteamDir();
|
||||
u8string GetRCTClassicSteamDir();
|
||||
datetime64 GetDatetimeNowUTC();
|
||||
uint32_t GetTicks();
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ namespace OpenRCT2::Platform
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetSteamPath()
|
||||
SteamPaths GetSteamPaths()
|
||||
{
|
||||
const char* homeDir = getpwuid(getuid())->pw_dir;
|
||||
if (homeDir == nullptr)
|
||||
@@ -244,27 +244,18 @@ namespace OpenRCT2::Platform
|
||||
}
|
||||
|
||||
auto steamPath = Path::Combine(homeDir, "Library/Application Support/Steam");
|
||||
if (Path::DirectoryExists(steamPath))
|
||||
if (!Path::DirectoryExists(steamPath))
|
||||
{
|
||||
return steamPath;
|
||||
return {};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
SteamPaths ret = {};
|
||||
ret.roots.emplace_back(steamPath);
|
||||
ret.nativeFolder = "steamapps/common";
|
||||
ret.downloadDepotFolder = "Steam.AppBundle/Steam/Contents/MacOS/steamapps/content";
|
||||
ret.manifests = "steamapps";
|
||||
|
||||
u8string GetRCT1SteamDir()
|
||||
{
|
||||
return u8"Steam.AppBundle/Steam/Contents/MacOS/steamapps/content/app_285310/depot_285311";
|
||||
}
|
||||
|
||||
u8string GetRCT2SteamDir()
|
||||
{
|
||||
return u8"Steam.AppBundle/Steam/Contents/MacOS/steamapps/content/app_285330/depot_285331";
|
||||
}
|
||||
|
||||
u8string GetRCTClassicSteamDir()
|
||||
{
|
||||
return u8"steamapps/common/RollerCoaster Tycoon Classic";
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string GetFontPath(const TTFFontDescriptor& font)
|
||||
|
||||
Reference in New Issue
Block a user