Launcher.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Launcher.h>
  9. #include <AzCore/Casting/numeric_cast.h>
  10. #include <AzCore/Component/ComponentApplicationLifecycle.h>
  11. #include <AzCore/Console/IConsole.h>
  12. #include <AzCore/Debug/BudgetTracker.h>
  13. #include <AzCore/Debug/Trace.h>
  14. #include <AzCore/Interface/Interface.h>
  15. #include <AzCore/IO/Path/Path.h>
  16. #include <AzCore/IO/SystemFile.h>
  17. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  18. #include <AzCore/std/smart_ptr/make_shared.h>
  19. #include <AzCore/StringFunc/StringFunc.h>
  20. #include <AzCore/Utils/Utils.h>
  21. #include <AzFramework/Asset/AssetSystemBus.h>
  22. #include <AzFramework/IO/RemoteStorageDrive.h>
  23. #include <AzFramework/Windowing/NativeWindow.h>
  24. #include <AzFramework/Windowing/WindowBus.h>
  25. #include <AzGameFramework/Application/GameApplication.h>
  26. #include <ISystem.h>
  27. #include <Launcher_Traits_Platform.h>
  28. #if defined(AZ_MONOLITHIC_BUILD)
  29. extern "C" void CreateStaticModules(AZStd::vector<AZ::Module*>& modulesOut);
  30. #endif // defined(AZ_MONOLITHIC_BUILD)
  31. // Add the "REMOTE_ASSET_PROCESSOR" define except in release
  32. // this makes it so that asset processor functions. Without this, all assets must be present and on local media
  33. // with this, the asset processor can be used to remotely process assets.
  34. #if !defined(_RELEASE)
  35. # define REMOTE_ASSET_PROCESSOR
  36. #endif
  37. void CVar_OnViewportPosition(const AZ::Vector2& value);
  38. namespace
  39. {
  40. void CVar_OnViewportResize(const AZ::Vector2& value);
  41. AZ_CVAR(AZ::Vector2, r_viewportSize, AZ::Vector2::CreateZero(), CVar_OnViewportResize, AZ::ConsoleFunctorFlags::DontReplicate,
  42. "The default size for the launcher viewport, 0 0 means full screen");
  43. void CVar_OnViewportResize(const AZ::Vector2& value)
  44. {
  45. AzFramework::NativeWindowHandle windowHandle = nullptr;
  46. AzFramework::WindowSystemRequestBus::BroadcastResult(windowHandle, &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
  47. AzFramework::WindowSize newSize = AzFramework::WindowSize(aznumeric_cast<int32_t>(value.GetX()), aznumeric_cast<int32_t>(value.GetY()));
  48. AzFramework::WindowRequestBus::Broadcast(&AzFramework::WindowRequestBus::Events::ResizeClientArea, newSize, AzFramework::WindowPosOptions());
  49. }
  50. AZ_CVAR(AZ::Vector2, r_viewportPos, AZ::Vector2::CreateZero(), CVar_OnViewportPosition, AZ::ConsoleFunctorFlags::DontReplicate,
  51. "The default position for the launcher viewport, 0 0 means top left corner of your main desktop");
  52. void ExecuteConsoleCommandFile(AzFramework::Application& application)
  53. {
  54. const AZStd::string_view customConCmdKey = "console-command-file";
  55. const AZ::CommandLine* commandLine = application.GetCommandLine();
  56. AZStd::size_t numSwitchValues = commandLine->GetNumSwitchValues(customConCmdKey);
  57. if (numSwitchValues > 0)
  58. {
  59. // The expectations for command line parameters is that the "last one wins"
  60. // That way it allows users and test scripts to override previous command line options by just listing them later on the invocation line
  61. const AZStd::string& consoleCmd = commandLine->GetSwitchValue(customConCmdKey, numSwitchValues - 1);
  62. if (!consoleCmd.empty())
  63. {
  64. AZ::Interface<AZ::IConsole>::Get()->ExecuteConfigFile(consoleCmd.c_str());
  65. }
  66. }
  67. }
  68. void RunMainLoop(AzGameFramework::GameApplication& gameApplication)
  69. {
  70. // Ideally we'd just call GameApplication::RunMainLoop instead, but
  71. // we'd have to stop calling ISystem::UpdatePreTickBus / PostTickBus
  72. // directly, and instead have something subscribe to the TickBus in
  73. // order to call them, using order ComponentTickBus::TICK_FIRST - 1
  74. // and ComponentTickBus::TICK_LAST + 1 to ensure they get called at
  75. // the same time as they do now. Also, we'd need to pass a function
  76. // pointer to AzGameFramework::GameApplication::MainLoop that would
  77. // be used to call ITimer::GetFrameTime (unless we could also shift
  78. // our frame time to be managed by AzGameFramework::GameApplication
  79. // instead, which probably isn't going to happen anytime soon given
  80. // how many things depend on the ITimer interface).
  81. ISystem* system = gEnv ? gEnv->pSystem : nullptr;
  82. while (!gameApplication.WasExitMainLoopRequested())
  83. {
  84. // Pump the system event loop
  85. gameApplication.PumpSystemEventLoopUntilEmpty();
  86. if (gameApplication.WasExitMainLoopRequested())
  87. {
  88. break;
  89. }
  90. // Update the AzFramework system tick bus
  91. gameApplication.TickSystem();
  92. // Pre-update CrySystem
  93. if (system)
  94. {
  95. system->UpdatePreTickBus();
  96. }
  97. // Update the AzFramework application tick bus
  98. gameApplication.Tick();
  99. // Post-update CrySystem
  100. if (system)
  101. {
  102. system->UpdatePostTickBus();
  103. }
  104. }
  105. }
  106. }
  107. namespace O3DELauncher
  108. {
  109. inline constexpr AZStd::string_view LauncherTypeTag = "/O3DE/Runtime/LauncherType";
  110. inline constexpr AZStd::string_view LauncherFilenameTag = "launcher";
  111. AZ_CVAR(bool, bg_ConnectToAssetProcessor, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "If true, the process will launch and connect to the asset processor");
  112. bool PlatformMainInfo::CopyCommandLine(int argc, char** argv)
  113. {
  114. for (int argIndex = 0; argIndex < argc; ++argIndex)
  115. {
  116. if (!AddArgument(argv[argIndex]))
  117. {
  118. return false;
  119. }
  120. }
  121. return true;
  122. }
  123. bool PlatformMainInfo::AddArgument(const char* arg)
  124. {
  125. AZ_Error("Launcher", arg, "Attempting to add a nullptr command line argument!");
  126. bool needsQuote = (strstr(arg, " ") != nullptr);
  127. bool needsSpace = (m_commandLine[0] != 0);
  128. // strip the previous null-term from the count to prevent double counting
  129. m_commandLineLen = (m_commandLineLen == 0) ? 0 : (m_commandLineLen - 1);
  130. // compute the expected length with the added argument
  131. size_t argLen = strlen(arg);
  132. size_t pendingLen = m_commandLineLen + argLen + 1 + (needsSpace ? 1 : 0) + (needsQuote ? 2 : 0); // +1 null-term, [+1 space], [+2 quotes]
  133. if (pendingLen >= AZ_COMMAND_LINE_LEN)
  134. {
  135. AZ_Assert(false, "Command line exceeds the %d character limit!", AZ_COMMAND_LINE_LEN);
  136. return false;
  137. }
  138. if (needsSpace)
  139. {
  140. m_commandLine[m_commandLineLen++] = ' ';
  141. }
  142. if (needsQuote) // Branching instead of using a ternary on the format string to avoid warning 4774 (format literal expected)
  143. {
  144. azsnprintf(m_commandLine + m_commandLineLen,
  145. AZ_COMMAND_LINE_LEN - m_commandLineLen,
  146. "\"%s\"",
  147. arg);
  148. }
  149. else
  150. {
  151. azsnprintf(m_commandLine + m_commandLineLen,
  152. AZ_COMMAND_LINE_LEN - m_commandLineLen,
  153. "%s",
  154. arg);
  155. }
  156. // Inject the argument in the argument buffer to preserve/replicate argC and argV
  157. azstrncpy(&m_commandLineArgBuffer[m_nextCommandLineArgInsertPoint],
  158. AZ_COMMAND_LINE_LEN - m_nextCommandLineArgInsertPoint,
  159. arg,
  160. argLen+1);
  161. m_argV[m_argC++] = &m_commandLineArgBuffer[m_nextCommandLineArgInsertPoint];
  162. m_nextCommandLineArgInsertPoint += argLen + 1;
  163. m_commandLineLen = pendingLen;
  164. return true;
  165. }
  166. const char* GetReturnCodeString(ReturnCode code)
  167. {
  168. switch (code)
  169. {
  170. case ReturnCode::Success:
  171. return "Success";
  172. case ReturnCode::ErrCommandLine:
  173. return "Failed to copy command line arguments";
  174. case ReturnCode::ErrResourceLimit:
  175. return "A resource limit failed to update";
  176. case ReturnCode::ErrAppDescriptor:
  177. return "Application descriptor file was not found";
  178. case ReturnCode::ErrCrySystemLib:
  179. return "Failed to load the CrySystem library";
  180. case ReturnCode::ErrCrySystemInterface:
  181. return "Failed to initialize the CrySystem Interface";
  182. case ReturnCode::ErrCryEnvironment:
  183. return "Failed to initialize the global environment";
  184. case ReturnCode::ErrAssetProccessor:
  185. return "Failed to connect to AssetProcessor while the /Amazon/AzCore/Bootstrap/wait_for_connect value is 1\n."
  186. "wait_for_connect can be set to 0 within the bootstrap to allow connecting to the AssetProcessor"
  187. " to not be an error if unsuccessful.";
  188. default:
  189. return "Unknown error code";
  190. }
  191. }
  192. void CreateRemoteFileIO();
  193. // This function make sure the launcher has signaled the "CriticalAssetsCompiled"
  194. // lifecycle event as well as to load the "assetcatalog.xml" file if it exists
  195. void CompileCriticalAssets()
  196. {
  197. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  198. {
  199. // Reload the assetcatalog.xml at this point again
  200. // Start Monitoring Asset changes over the network and load the AssetCatalog.
  201. // Note: When using VFS this is the first time catalog will be loaded using remote's catalog file.
  202. auto LoadCatalog = [settingsRegistry](AZ::Data::AssetCatalogRequests* assetCatalogRequests)
  203. {
  204. if (AZ::IO::FixedMaxPath assetCatalogPath;
  205. settingsRegistry->Get(assetCatalogPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
  206. {
  207. assetCatalogPath /= "assetcatalog.xml";
  208. assetCatalogRequests->LoadCatalog(assetCatalogPath.c_str());
  209. }
  210. };
  211. AZ::Data::AssetCatalogRequestBus::Broadcast(AZStd::move(LoadCatalog));
  212. AZ_TracePrintf("Launcher", "CriticalAssetsCompiled\n");
  213. // Broadcast that critical assets are ready
  214. AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "CriticalAssetsCompiled", R"({})");
  215. }
  216. }
  217. // If the connect option is false, this function will return true
  218. // to make sure the Launcher passes the connected to AP check
  219. // If REMOTE_ASSET_PROCESSOR is not defined, then the launcher doesn't need
  220. // to connect to the AssetProcessor and therefore this function returns true
  221. bool ConnectToAssetProcessor([[maybe_unused]] bool connect)
  222. {
  223. bool connectedToAssetProcessor = true;
  224. #if defined(REMOTE_ASSET_PROCESSOR)
  225. if (connect)
  226. {
  227. // When the AssetProcessor is already launched it should take less than a second to perform a connection
  228. // but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize
  229. // and able to negotiate a connection when running a debug build
  230. // and to negotiate a connection
  231. // Setting the connectTimeout to 3 seconds if not set within the settings registry
  232. AzFramework::AssetSystem::ConnectionSettings connectionSettings;
  233. AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
  234. connectionSettings.m_launchAssetProcessorOnFailedConnection = true;
  235. connectionSettings.m_connectionIdentifier = AzFramework::AssetSystem::ConnectionIdentifiers::Game;
  236. connectionSettings.m_loggingCallback = []([[maybe_unused]] AZStd::string_view logData)
  237. {
  238. AZ_TracePrintf("Launcher", "%.*s", aznumeric_cast<int>(logData.size()), logData.data());
  239. };
  240. AzFramework::AssetSystemRequestBus::BroadcastResult(connectedToAssetProcessor, &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
  241. if (connectedToAssetProcessor)
  242. {
  243. AZ_TracePrintf("Launcher", "Connected to Asset Processor\n");
  244. CreateRemoteFileIO();
  245. }
  246. }
  247. #endif
  248. CompileCriticalAssets();
  249. return connectedToAssetProcessor;
  250. }
  251. //! Remote FileIO to use as a Virtual File System
  252. //! Communication of FileIOBase operations occur through an AssetProcessor connection
  253. void CreateRemoteFileIO()
  254. {
  255. AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
  256. AZ::s64 allowRemoteFilesystem{};
  257. AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, allowRemoteFilesystem,
  258. AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "remote_filesystem");
  259. if (allowRemoteFilesystem != 0)
  260. {
  261. using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
  262. // Application::StartCommon will set a LocalFileIO base first.
  263. // This provides an opportunity for the RemoteFileIO to override the direct instance
  264. auto remoteFileIo = new AZ::IO::RemoteFileIO(AZ::IO::FileIOBase::GetDirectInstance()); // Wrap LocalFileIO the direct instance
  265. // SetDirectInstance will assert if this has already been set and we don't clear first
  266. AZ::IO::FileIOBase::SetDirectInstance(nullptr);
  267. // Wrap AZ:IO::LocalFileIO the direct instance
  268. AZ::IO::FileIOBase::SetDirectInstance(remoteFileIo);
  269. // Set file paths to uses aliases, they will be resolved by the remote file system.
  270. // Prefixing alias with / so they are treated as absolute paths by Path class,
  271. // otherwise odd concatenations of aliases happen leading to invalid paths when
  272. // resolved by the remote system.
  273. settingsRegistry->Set(FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/engine_path", "/@engroot@");
  274. settingsRegistry->Set(FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path", "/@projectroot@");
  275. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, "/@engroot@");
  276. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath, "/@projectroot@");
  277. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder, "/@products@");
  278. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath, "/@user@");
  279. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectLogPath, "/@log@");
  280. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage, "/@usercache@");
  281. }
  282. }
  283. ReturnCode Run(const PlatformMainInfo& mainInfo)
  284. {
  285. if (mainInfo.m_updateResourceLimits
  286. && !mainInfo.m_updateResourceLimits())
  287. {
  288. return ReturnCode::ErrResourceLimit;
  289. }
  290. // Game Application (AzGameFramework)
  291. int gameArgC = mainInfo.m_argC;
  292. char** gameArgV = const_cast<char**>(mainInfo.m_argV);
  293. constexpr size_t MaxCommandArgsCount = 128;
  294. using ArgumentContainer = AZStd::fixed_vector<char*, MaxCommandArgsCount>;
  295. ArgumentContainer argContainer(gameArgV, gameArgV + gameArgC);
  296. using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
  297. // Initialize the Settings Registry with the Engine Path, Project Path and Project Name settings
  298. // Add the initial JSON object for "/O3DE/Runtime/Manifest/Project" and "/Amazon/AzCore/Bootstrap"
  299. FixedValueString launcherJsonPatch = R"(
  300. [
  301. { "op": "add", "path": "/O3DE", "value": { "Runtime": { "Manifest": { "Project": {} } } } },
  302. { "op": "add", "path": "/Amazon", "value": { "AzCore": { "Bootstrap": {} } } })";
  303. // Query the project_name baked into the launcher executable
  304. const AZStd::string_view launcherProjectName = GetProjectName();
  305. if (!launcherProjectName.empty())
  306. {
  307. // Append the project name setting to the JSON Patch
  308. launcherJsonPatch += FixedValueString::format(R"(,
  309. { "op": "add", "path": "/O3DE/Runtime/Manifest/Project/project_name", "value": "%.*s" })", AZ_STRING_ARG(launcherProjectName));
  310. }
  311. // Non-host platforms cannot use the project path that is #defined within the launcher.
  312. // In this case the the result of AZ::Utils::GetDefaultAppRoot is used instead
  313. #if !AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
  314. AZStd::string_view projectPath;
  315. // Make sure the defaultAppRootPath variable is in scope long enough until the projectPath string_view is used below
  316. AZStd::optional<AZ::IO::FixedMaxPathString> defaultAppRootPath = AZ::Utils::GetDefaultAppRootPath();
  317. if (defaultAppRootPath.has_value())
  318. {
  319. projectPath = *defaultAppRootPath;
  320. }
  321. if (!projectPath.empty())
  322. {
  323. launcherJsonPatch += FixedValueString::format(R"(,
  324. { "op": "add", "path": "/Amazon/AzCore/Bootstrap/project_path", "value": "%.*s" })", AZ_STRING_ARG(projectPath));
  325. // For non-host platforms set the engine root to be the project root
  326. // Since the directories available during execution are limited on those platforms
  327. AZStd::string_view enginePath = projectPath;
  328. launcherJsonPatch += FixedValueString::format(R"(,
  329. { "op": "add", "path": "/Amazon/AzCore/Bootstrap/engine_path", "value": "%.*s" })", AZ_STRING_ARG(enginePath));
  330. }
  331. #endif
  332. // Now terminate the JSON Patch array with a trailing ']'
  333. launcherJsonPatch += R"(
  334. ])";
  335. AZ::ComponentApplicationSettings componentAppSettings;
  336. componentAppSettings.m_setregBootstrapJson = launcherJsonPatch;
  337. // Treat the bootstrap JSON as being in JSON Patch format
  338. componentAppSettings.m_setregFormat = AZ::SettingsRegistryInterface::Format::JsonPatch;
  339. AzGameFramework::GameApplication gameApplication(aznumeric_cast<int>(argContainer.size()), argContainer.data(), AZStd::move(componentAppSettings));
  340. // The settings registry has been created by the AZ::ComponentApplication constructor at this point
  341. auto settingsRegistry = AZ::SettingsRegistry::Get();
  342. if (settingsRegistry == nullptr)
  343. {
  344. // Settings registry must be available at this point in order to continue
  345. return ReturnCode::ErrValidation;
  346. }
  347. // Save the build target name (usually myprojectname_gamelauncher, or myprojectname_serverlauncher, etc)
  348. // into the specialization list, so that the regset files for xxxxx.myprojectname_gamelauncher are included in the loaded set.
  349. // in generic mode, this needs to be updated to a name based on the project name, so it is not a string view, here.
  350. AZ::SettingsRegistryInterface::FixedValueString buildTargetName(GetBuildTargetName());
  351. // retrieve the project name as specified by the actual project.json (or updated from command line)
  352. AZ::SettingsRegistryInterface::FixedValueString updatedProjectName = AZ::Utils::GetProjectName();
  353. if (IsGenericLauncher())
  354. {
  355. constexpr AZStd::string_view O3DEPrefix = "O3DE_";
  356. // this will always be the value O3DE_xxxxx where xxxxx is the type of target ("GameLauncher/ServerLauncher/UnifiedLauncher/etc")
  357. // and O3DE is a placeholder for the project name. Replace the "O3DE_" part with "{ProjectName}_" (keeping the underscore).
  358. if (buildTargetName.starts_with(O3DEPrefix))
  359. {
  360. auto replacementName = AZ::SettingsRegistryInterface::FixedValueString::format(
  361. "%.*s_", aznumeric_cast<int>(updatedProjectName.size()), updatedProjectName.data());
  362. buildTargetName.replace(0, O3DEPrefix.size(), replacementName);
  363. }
  364. }
  365. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(*settingsRegistry, buildTargetName);
  366. //Store the launcher type to the Settings Registry
  367. AZStd::string_view launcherType = GetLauncherTypeSpecialization();
  368. settingsRegistry->Set(LauncherTypeTag, launcherType);
  369. // Also add the launcher type as a specialization as well
  370. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddSpecialization(*settingsRegistry, launcherType);
  371. #if O3DE_HEADLESS_SERVER
  372. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddSpecialization(*settingsRegistry, "headless");
  373. gameApplication.SetHeadless(true);
  374. #else
  375. gameApplication.SetHeadless(false);
  376. #endif // O3DE_HEADLESS_SERVER
  377. #if AZ_TRAIT_CONSOLE_MODE_SUPPORT
  378. gameApplication.SetConsoleModeSupported(true);
  379. #else
  380. gameApplication.SetConsoleModeSupported(false);
  381. #endif // AZ_TRAIT_CONSOLE_MODE_SUPPORT
  382. // Finally add the "launcher" specialization tag into the Settings Registry
  383. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddSpecialization(*settingsRegistry, LauncherFilenameTag);
  384. AZ_TracePrintf("Launcher", R"(Running project "%.*s")" "\n"
  385. R"(The project name has been successfully set in the Settings Registry at key "%s/project_name")"
  386. R"( for Launcher target "%.*s")" "\n",
  387. aznumeric_cast<int>(updatedProjectName.size()), updatedProjectName.data(),
  388. AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey,
  389. aznumeric_cast<int>(buildTargetName.size()), buildTargetName.data());
  390. AZ::SettingsRegistryInterface::FixedValueString pathToAssets;
  391. if (!settingsRegistry->Get(pathToAssets, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
  392. {
  393. // Default to mainInfo.m_appResource if the cache root folder is missing from the Settings Registry
  394. pathToAssets = mainInfo.m_appResourcesPath;
  395. AZ_Error("Launcher", false, "Unable to retrieve asset cache root folder from the settings registry at json pointer path %s",
  396. AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
  397. }
  398. else
  399. {
  400. AZ_TracePrintf("Launcher", "The asset cache folder of %s has been successfully read from the Settings Registry\n",
  401. pathToAssets.c_str());
  402. }
  403. // System Init Params ("Legacy" Open 3D Engine)
  404. SSystemInitParams systemInitParams;
  405. memset(&systemInitParams, 0, sizeof(SSystemInitParams));
  406. {
  407. AzGameFramework::GameApplication::StartupParameters gameApplicationStartupParams;
  408. #if defined(AZ_MONOLITHIC_BUILD)
  409. gameApplicationStartupParams.m_createStaticModulesCallback = CreateStaticModules;
  410. gameApplicationStartupParams.m_loadDynamicModules = false;
  411. #endif // defined(AZ_MONOLITHIC_BUILD)
  412. const char* isDedicatedServerCommand = IsDedicatedServer() ? "sv_isDedicated true" : "sv_isDedicated false";
  413. AZ::Interface<AZ::IConsole>::Get()->PerformCommand(isDedicatedServerCommand);
  414. gameApplication.Start({}, gameApplicationStartupParams);
  415. //connect to the asset processor using the bootstrap values
  416. const bool allowedEngineConnection = !systemInitParams.bToolMode && !systemInitParams.bTestMode && bg_ConnectToAssetProcessor;
  417. if (!ConnectToAssetProcessor(allowedEngineConnection))
  418. {
  419. AZ::s64 waitForConnect{};
  420. AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, waitForConnect,
  421. AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "wait_for_connect");
  422. if (waitForConnect != 0)
  423. {
  424. AZ_Error("Launcher", false, "Failed to connect to AssetProcessor.");
  425. return ReturnCode::ErrAssetProccessor;
  426. }
  427. }
  428. //Initialize the Debug trace instance to create necessary environment variables
  429. AZ::Debug::Trace::Instance().Init();
  430. if (!IsDedicatedServer() && !systemInitParams.bToolMode && !systemInitParams.bTestMode)
  431. {
  432. if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI != nullptr)
  433. {
  434. nativeUI->SetMode(AZ::NativeUI::Mode::ENABLED);
  435. }
  436. }
  437. }
  438. if (mainInfo.m_onPostAppStart)
  439. {
  440. mainInfo.m_onPostAppStart();
  441. }
  442. azstrncpy(systemInitParams.szSystemCmdLine, sizeof(systemInitParams.szSystemCmdLine),
  443. mainInfo.m_commandLine, mainInfo.m_commandLineLen);
  444. systemInitParams.sLogFileName = GetLogFilename();
  445. systemInitParams.hInstance = mainInfo.m_instance;
  446. systemInitParams.hWnd = mainInfo.m_window;
  447. systemInitParams.pPrintSync = mainInfo.m_printSink;
  448. systemInitParams.bDedicatedServer = IsDedicatedServer();
  449. AZ::s64 remoteFileSystemEnabled{};
  450. AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, remoteFileSystemEnabled,
  451. AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "remote_filesystem");
  452. if (remoteFileSystemEnabled != 0)
  453. {
  454. // Reset local variable pathToAssets now that it's using remote file system
  455. pathToAssets = "";
  456. settingsRegistry->Get(pathToAssets, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
  457. AZ_TracePrintf("Launcher", "Application is configured for VFS");
  458. AZ_TracePrintf("Launcher", "Log and cache files will be written to the Cache directory on your host PC");
  459. #if defined(AZ_ENABLE_TRACING)
  460. constexpr const char* message = "If your game does not run, check any of the following:\n"
  461. "\t- Verify the remote_ip address is correct in bootstrap.cfg";
  462. #endif
  463. if (mainInfo.m_additionalVfsResolution)
  464. {
  465. AZ_TracePrintf("Launcher", "%s\n%s", message, mainInfo.m_additionalVfsResolution)
  466. }
  467. else
  468. {
  469. AZ_TracePrintf("Launcher", "%s", message)
  470. }
  471. }
  472. else
  473. {
  474. AZ_TracePrintf("Launcher", "Application is configured to use device local files at %s\n", pathToAssets.c_str());
  475. AZ_TracePrintf("Launcher", "Log and cache files will be written to device storage\n");
  476. }
  477. // Create CrySystem.
  478. #if !defined(AZ_MONOLITHIC_BUILD)
  479. constexpr const char* crySystemLibraryName = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX "CrySystem" AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
  480. AZStd::unique_ptr<AZ::DynamicModuleHandle> crySystemLibrary = AZ::DynamicModuleHandle::Create(crySystemLibraryName);
  481. if (crySystemLibrary->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired))
  482. {
  483. PFNCREATESYSTEMINTERFACE CreateSystemInterface =
  484. crySystemLibrary->GetFunction<PFNCREATESYSTEMINTERFACE>("CreateSystemInterface");
  485. if (CreateSystemInterface)
  486. {
  487. systemInitParams.pSystem = CreateSystemInterface(systemInitParams);
  488. }
  489. }
  490. #else
  491. systemInitParams.pSystem = CreateSystemInterface(systemInitParams);
  492. #endif // !defined(AZ_MONOLITHIC_BUILD)
  493. AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacySystemInterfaceCreated", R"({})");
  494. ReturnCode status = ReturnCode::Success;
  495. if (systemInitParams.pSystem)
  496. {
  497. // Process queued events before main loop.
  498. AZ::TickBus::ExecuteQueuedEvents();
  499. #if !defined(SYS_ENV_AS_STRUCT)
  500. gEnv = systemInitParams.pSystem->GetGlobalEnvironment();
  501. #endif // !defined(SYS_ENV_AS_STRUCT)
  502. if (gEnv && gEnv->pConsole)
  503. {
  504. // Execute autoexec.cfg to load the initial level
  505. auto autoExecFile = AZ::IO::FixedMaxPath{pathToAssets} / "autoexec.cfg";
  506. AZ::Interface<AZ::IConsole>::Get()->ExecuteConfigFile(autoExecFile.Native());
  507. // Find out if console command file was passed
  508. // via --console-command-file=%filename% and execute it
  509. ExecuteConsoleCommandFile(gameApplication);
  510. gEnv->pSystem->ExecuteCommandLine(false);
  511. AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacyCommandLineProcessed", R"({})");
  512. // Run the main loop
  513. RunMainLoop(gameApplication);
  514. }
  515. else
  516. {
  517. status = ReturnCode::ErrCryEnvironment;
  518. }
  519. }
  520. else
  521. {
  522. status = ReturnCode::ErrCrySystemInterface;
  523. }
  524. #if !defined(AZ_MONOLITHIC_BUILD)
  525. #if !defined(_RELEASE)
  526. // until CrySystem can be removed (or made to be managed by the component application),
  527. // we need to manually clear the BudgetTracker before CrySystem is unloaded so the Budget
  528. // pointer(s) it has references to are cleared properly
  529. if (auto budgetTracker = AZ::Interface<AZ::Debug::BudgetTracker>::Get(); budgetTracker)
  530. {
  531. budgetTracker->Reset();
  532. }
  533. #endif // !defined(_RELEASE)
  534. // The order of operations here is to delete CrySystem, stop the game application, then unload the CrySystem dll.
  535. // If we unloaded the CrySystem dll before stopping the game application, we can potentially have crashes
  536. // if the CrySystem dll created any EBus contexts, since those contexts would get destroyed before subsystems could
  537. // disconnect from the buses.
  538. SAFE_DELETE(systemInitParams.pSystem);
  539. gameApplication.Stop();
  540. crySystemLibrary.reset(nullptr);
  541. #else
  542. SAFE_DELETE(systemInitParams.pSystem);
  543. gameApplication.Stop();
  544. #endif // !defined(AZ_MONOLITHIC_BUILD)
  545. AZ::Debug::Trace::Instance().Destroy();
  546. return status;
  547. }
  548. }