/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include "CrySystem_precompiled.h" #include "System.h" #if defined(AZ_RESTRICTED_PLATFORM) || defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS) #undef AZ_RESTRICTED_SECTION #define SYSTEMINIT_CPP_SECTION_1 1 #define SYSTEMINIT_CPP_SECTION_2 2 #define SYSTEMINIT_CPP_SECTION_3 3 #define SYSTEMINIT_CPP_SECTION_4 4 #define SYSTEMINIT_CPP_SECTION_5 5 #define SYSTEMINIT_CPP_SECTION_6 6 #define SYSTEMINIT_CPP_SECTION_7 7 #define SYSTEMINIT_CPP_SECTION_8 8 #define SYSTEMINIT_CPP_SECTION_9 9 #define SYSTEMINIT_CPP_SECTION_10 10 #define SYSTEMINIT_CPP_SECTION_11 11 #define SYSTEMINIT_CPP_SECTION_12 12 #define SYSTEMINIT_CPP_SECTION_13 13 #define SYSTEMINIT_CPP_SECTION_14 14 #define SYSTEMINIT_CPP_SECTION_15 15 #define SYSTEMINIT_CPP_SECTION_16 16 #define SYSTEMINIT_CPP_SECTION_17 17 #endif #include "CryLibrary.h" #include "CryPath.h" #include #include #include // for AZ_MAX_PATH_LEN #include #include #include #include #include #include #include #include #include #include "AZCoreLogSink.h" #include #include #include #include #include #include #include #include #include #include #if defined(APPLE) || defined(LINUX) #include #include #endif #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" #include // To enable profiling with vtune (https://software.intel.com/en-us/intel-vtune-amplifier-xe), make sure the line below is not commented out //#define PROFILE_WITH_VTUNE #endif //WIN32 #include #include #include #include #include #include #include #include #include #include #include "XConsole.h" #include "Log.h" #include "XML/xml.h" #include "LocalizedStringManager.h" #include "SystemEventDispatcher.h" #include "LevelSystem/LevelSystem.h" #include "LevelSystem/SpawnableLevelSystem.h" #include "ViewSystem/ViewSystem.h" #include #include #include #if defined(ANDROID) #include #endif #if defined(EXTERNAL_CRASH_REPORTING) #include #endif // select the asset processor based on cvars and defines. // uncomment the following and edit the path where it is instantiated if you'd like to use the test file client //#define USE_TEST_FILE_CLIENT #if defined(REMOTE_ASSET_PROCESSOR) // Over here, we'd put the header to the Remote Asset Processor interface (as opposed to the Local built in version below) # include #endif #ifdef WIN32 extern LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers); #endif #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_14 #include AZ_RESTRICTED_FILE(SystemInit_cpp) #endif #if AZ_TRAIT_USE_CRY_SIGNAL_HANDLER #include #include void CryEngineSignalHandler(int signal) { char resolvedPath[_MAX_PATH]; // it is assumed that @log@ points at the appropriate place (so for apple, to the user profile dir) if (AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath("@log@/crash.log", resolvedPath, _MAX_PATH)) { fprintf(stderr, "Crash Signal Handler - logged to %s\n", resolvedPath); FILE* file = fopen(resolvedPath, "a"); if (file) { char sTime[128]; time_t ltime; time(<ime); struct tm* today = localtime(<ime); strftime(sTime, 40, "<%Y-%m-%d %H:%M:%S> ", today); fprintf(file, "%s: Error: signal %s:\n", sTime, strsignal(signal)); fflush(file); void* array[100]; int s = backtrace(array, 100); backtrace_symbols_fd(array, s, fileno(file)); fclose(file); CryLogAlways("Successfully recorded crash file: '%s'", resolvedPath); abort(); } } CryLogAlways("Could not record crash file..."); abort(); } #endif // AZ_TRAIT_USE_CRY_SIGNAL_HANDLER ////////////////////////////////////////////////////////////////////////// #define DEFAULT_LOG_FILENAME "@log@/Log.txt" #define CRYENGINE_ENGINE_FOLDER "Engine" ////////////////////////////////////////////////////////////////////////// #define CRYENGINE_DEFAULT_LOCALIZATION_LANG "en-US" #define LOCALIZATION_TRANSLATIONS_LIST_FILE_NAME "Libs/Localization/localization.xml" ////////////////////////////////////////////////////////////////////////// #if defined(WIN32) || defined(LINUX) || defined(APPLE) # define DLL_MODULE_INIT_ISYSTEM "ModuleInitISystem" # define DLL_MODULE_SHUTDOWN_ISYSTEM "ModuleShutdownISystem" # define DLL_INITFUNC_RENDERER "PackageRenderConstructor" # define DLL_INITFUNC_SOUND "CreateSoundSystem" # define DLL_INITFUNC_FONT "CreateCryFontInterface" # define DLL_INITFUNC_3DENGINE "CreateCry3DEngine" # define DLL_INITFUNC_UI "CreateLyShineInterface" #define AZ_RESTRICTED_SECTION_IMPLEMENTED #elif defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_1 #include AZ_RESTRICTED_FILE(SystemInit_cpp) #endif #if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED) #undef AZ_RESTRICTED_SECTION_IMPLEMENTED #else # define DLL_MODULE_INIT_ISYSTEM (LPCSTR)2 # define DLL_MODULE_SHUTDOWN_ISYSTEM (LPCSTR)3 # define DLL_INITFUNC_RENDERER (LPCSTR)1 # define DLL_INITFUNC_RENDERER (LPCSTR)1 # define DLL_INITFUNC_SOUND (LPCSTR)1 # define DLL_INITFUNC_PHYSIC (LPCSTR)1 # define DLL_INITFUNC_FONT (LPCSTR)1 # define DLL_INITFUNC_3DENGINE (LPCSTR)1 # define DLL_INITFUNC_UI (LPCSTR)1 #endif #define AZ_TRACE_SYSTEM_WINDOW AZ::Debug::Trace::GetDefaultSystemWindow() #ifdef WIN32 extern HMODULE gDLLHandle; #endif namespace { #if defined(AZ_PLATFORM_WINDOWS) // on windows, we lock our cache using a lockfile. On other platforms this is not necessary since devices like ios, android, consoles cannot // run more than one game process that uses the same folder anyway. HANDLE g_cacheLock = INVALID_HANDLE_VALUE; #endif } //static int g_sysSpecChanged = false; struct SCVarsClientConfigSink : public ILoadConfigurationEntrySink { virtual void OnLoadConfigurationEntry(const char* szKey, const char* szValue, [[maybe_unused]] const char* szGroup) { gEnv->pConsole->SetClientDataProbeString(szKey, szValue); } }; ////////////////////////////////////////////////////////////////////////// static inline void InlineInitializationProcessing([[maybe_unused]] const char* sDescription) { if (gEnv->pLog) { gEnv->pLog->UpdateLoadingScreen(0); } } ////////////////////////////////////////////////////////////////////////// AZ_PUSH_DISABLE_WARNING(4723, "-Wunknown-warning-option") // potential divide by 0 (needs to wrap the function) static void CmdCrashTest(IConsoleCmdArgs* pArgs) { assert(pArgs); if (pArgs->GetArgCount() == 2) { //This method intentionally crashes, a lot. int crashType = atoi(pArgs->GetArg(1)); switch (crashType) { case 1: { int* p = 0; *p = 0xABCD; } break; case 2: { float a = 1.0f; memset(&a, 0, sizeof(a)); [[maybe_unused]] float* b = &a; [[maybe_unused]] float c = 3; CryLog("%f", (c / *b)); } break; case 3: while (true) { new char[10240]; } break; case 4: CryFatalError("sys_crashtest 4"); break; case 5: while (true) { new char[128]; //testing the crash handler an exception in the cry memory allocation occurred } case 6: { AZ_Assert(false, "Testing assert for testing crashes"); } break; case 7: __debugbreak(); break; case 8: CrySleep(1000 * 60 * 10); break; } } } AZ_POP_DISABLE_WARNING ////////////////////////////////////////////////////////////////////////// struct SysSpecOverrideSink : public ILoadConfigurationEntrySink { virtual void OnLoadConfigurationEntry(const char* szKey, const char* szValue, const char* szGroup) { ICVar* pCvar = gEnv->pConsole->GetCVar(szKey); if (pCvar) { const bool wasNotInConfig = ((pCvar->GetFlags() & VF_WASINCONFIG) == 0); bool applyCvar = wasNotInConfig; if (applyCvar == false) { // Special handling for sys_spec_full if (azstricmp(szKey, "sys_spec_full") == 0) { // If it is set to 0 then ignore this request to set to something else // If it is set to 0 then the user wants to changes system spec settings in system.cfg if (pCvar->GetIVal() != 0) { applyCvar = true; } } else { // This could bypass the restricted cvar checks that exist elsewhere depending on // the calling code so we also need check here before setting. bool isConst = pCvar->IsConstCVar(); bool isCheat = ((pCvar->GetFlags() & (VF_CHEAT | VF_CHEAT_NOCHECK | VF_CHEAT_ALWAYS_CHECK)) != 0); bool isReadOnly = ((pCvar->GetFlags() & VF_READONLY) != 0); bool isDeprecated = ((pCvar->GetFlags() & VF_DEPRECATED) != 0); bool allowApplyCvar = true; if ((isConst || isCheat || isReadOnly) || isDeprecated) { allowApplyCvar = !isDeprecated && (gEnv->pSystem->IsDevMode()) || (gEnv->IsEditor()); } if ((allowApplyCvar) || ALLOW_CONST_CVAR_MODIFICATIONS) { applyCvar = true; } } } if (applyCvar) { pCvar->Set(szValue); } else { CryLogAlways("NOT VF_WASINCONFIG Ignoring cvar '%s' new value '%s' old value '%s' group '%s'", szKey, szValue, pCvar->GetString(), szGroup); } } else { CryLogAlways("Can't find cvar '%s' value '%s' group '%s'", szKey, szValue, szGroup); } } }; #if !defined(CONSOLE) struct SysSpecOverrideSinkConsole : public ILoadConfigurationEntrySink { virtual void OnLoadConfigurationEntry(const char* szKey, const char* szValue, const char* szGroup) { // Ignore platform-specific cvars that should just be executed on the console if (azstricmp(szGroup, "Platform") == 0) { return; } ICVar* pCvar = gEnv->pConsole->GetCVar(szKey); if (pCvar) { pCvar->Set(szValue); } else { // If the cvar doesn't exist, calling this function only saves the value in case it's registered later where // at that point it will be set from the stored value. This is required because otherwise registering the // cvar bypasses any callbacks and uses values directly from the cvar group files. gEnv->pConsole->LoadConfigVar(szKey, szValue); } } }; #endif static ESystemConfigPlatform GetDevicePlatform() { #if defined(AZ_PLATFORM_WINDOWS) || defined(AZ_PLATFORM_LINUX) return CONFIG_PC; #define AZ_RESTRICTED_SECTION_IMPLEMENTED #elif defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_2 #include AZ_RESTRICTED_FILE(SystemInit_cpp) #endif #if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED) #undef AZ_RESTRICTED_SECTION_IMPLEMENTED #elif defined(AZ_PLATFORM_ANDROID) return CONFIG_ANDROID; #elif defined(AZ_PLATFORM_IOS) return CONFIG_IOS; #elif defined(AZ_PLATFORM_MAC) return CONFIG_OSX_METAL; #else AZ_Assert(false, "Platform not supported"); return CONFIG_INVALID_PLATFORM; #endif } ////////////////////////////////////////////////////////////////////////// #if !defined(AZ_MONOLITHIC_BUILD) AZStd::unique_ptr CSystem::LoadDynamiclibrary(const char* dllName) const { AZStd::unique_ptr handle = AZ::DynamicModuleHandle::Create(dllName); bool libraryLoaded = handle->Load(false); // We need to inject the environment first thing so that allocators are available immediately InjectEnvironmentFunction injectEnv = handle->GetFunction(INJECT_ENVIRONMENT_FUNCTION); if (injectEnv) { auto env = AZ::Environment::GetInstance(); injectEnv(env); } if (!libraryLoaded) { handle.release(); } return handle; } ////////////////////////////////////////////////////////////////////////// AZStd::unique_ptr CSystem::LoadDLL(const char* dllName) { AZ_TracePrintf(AZ_TRACE_SYSTEM_WINDOW, "Loading DLL: %s", dllName); AZStd::unique_ptr handle = LoadDynamiclibrary(dllName); if (!handle) { #if defined(LINUX) || defined(APPLE) AZ_Assert(false, "Error loading dylib: %s, error : %s\n", dllName, dlerror()); #else AZ_Assert(false, "Error loading dll: %s, error code %d", dllName, GetLastError()); #endif return handle; } ////////////////////////////////////////////////////////////////////////// // After loading DLL initialize it by calling ModuleInitISystem ////////////////////////////////////////////////////////////////////////// AZStd::string moduleName = PathUtil::GetFileName(dllName); typedef void*(*PtrFunc_ModuleInitISystem)(ISystem* pSystem, const char* moduleName); PtrFunc_ModuleInitISystem pfnModuleInitISystem = handle->GetFunction(DLL_MODULE_INIT_ISYSTEM); if (pfnModuleInitISystem) { pfnModuleInitISystem(this, moduleName.c_str()); } return handle; } // TODO:DLL #endif //#if defined(AZ_HAS_DLL_SUPPORT) && !defined(AZ_MONOLITHIC_BUILD) #endif //if !defined(AZ_MONOLITHIC_BUILD) ////////////////////////////////////////////////////////////////////////// bool CSystem::LoadEngineDLLs() { return true; } ////////////////////////////////////////////////////////////////////////// bool CSystem::UnloadDLL(const char* dllName) { bool isSuccess = false; AZ::Crc32 key(dllName); AZStd::unique_ptr empty; AZStd::unique_ptr& hModule = stl::find_in_map_ref(m_moduleDLLHandles, key, empty); if ((hModule) && (hModule->IsLoaded())) { DetachEnvironmentFunction detachEnv = hModule->GetFunction(DETACH_ENVIRONMENT_FUNCTION); if (detachEnv) { detachEnv(); } isSuccess = hModule->Unload(); hModule.release(); } return isSuccess; } ////////////////////////////////////////////////////////////////////////// void CSystem::ShutdownModuleLibraries() { #if !defined(AZ_MONOLITHIC_BUILD) for (auto iterator = m_moduleDLLHandles.begin(); iterator != m_moduleDLLHandles.end(); ++iterator) { typedef void*( * PtrFunc_ModuleShutdownISystem )(ISystem* pSystem); PtrFunc_ModuleShutdownISystem pfnModuleShutdownISystem = iterator->second->GetFunction(DLL_MODULE_SHUTDOWN_ISYSTEM); if (pfnModuleShutdownISystem) { pfnModuleShutdownISystem(this); } if (iterator->second->IsLoaded()) { iterator->second->Unload(); } iterator->second.release(); } m_moduleDLLHandles.clear(); #endif // !defined(AZ_MONOLITHIC_BUILD) } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// bool CSystem::InitConsole() { if (m_env.pConsole) { m_env.pConsole->Init(this); } return true; } ////////////////////////////////////////////////////////////////////////// // attaches the given variable to the given container; // recreates the variable if necessary ICVar* CSystem::attachVariable (const char* szVarName, int* pContainer, const char* szComment, int dwFlags) { IConsole* pConsole = GetIConsole(); ICVar* pOldVar = pConsole->GetCVar (szVarName); int nDefault = 0; if (pOldVar) { nDefault = pOldVar->GetIVal(); pConsole->UnregisterVariable(szVarName, true); } // NOTE: maybe we should preserve the actual value of the variable across the registration, // because of the strange architecture of IConsole that converts int->float->int REGISTER_CVAR2(szVarName, pContainer, *pContainer, dwFlags, szComment); ICVar* pVar = pConsole->GetCVar(szVarName); #ifdef _DEBUG // test if the variable really has this container assert (*pContainer == pVar->GetIVal()); ++*pContainer; assert (*pContainer == pVar->GetIVal()); --*pContainer; #endif if (pOldVar) { // carry on the default value from the old variable anyway pVar->Set(nDefault); } return pVar; } ///////////////////////////////////////////////////////////////////////////////// bool CSystem::InitFileSystem() { using namespace AzFramework::AssetSystem; if (m_pUserCallback) { m_pUserCallback->OnInitProgress("Initializing File System..."); } // get the DirectInstance FileIOBase which should be the AZ::LocalFileIO m_env.pFileIO = AZ::IO::FileIOBase::GetDirectInstance(); m_env.pCryPak = AZ::Interface::Get(); m_env.pFileIO = AZ::IO::FileIOBase::GetInstance(); AZ_Assert(m_env.pCryPak, "CryPak has not been initialized on AZ::Interface"); AZ_Assert(m_env.pFileIO, "FileIOBase has not been initialized"); if (m_bEditor) { m_env.pCryPak->RecordFileOpen(AZ::IO::IArchive::RFOM_EngineStartup); } // Now that file systems are init, we will clear any events that have arrived // during file system init, so that systems do not reload assets that were already compiled in the // critical compilation section. AzFramework::LegacyAssetEventBus::ClearQueuedEvents(); //we are good to go return true; } void CSystem::ShutdownFileSystem() { #if defined(AZ_PLATFORM_WINDOWS) if (g_cacheLock != INVALID_HANDLE_VALUE) { CloseHandle(g_cacheLock); g_cacheLock = INVALID_HANDLE_VALUE; } #endif using namespace AZ::IO; FileIOBase* directInstance = FileIOBase::GetDirectInstance(); FileIOBase* pakInstance = FileIOBase::GetInstance(); if (directInstance == m_env.pFileIO) { // we only mess with file io if we own the instance that we installed. // if we dont' own the instance, then we never configured fileIO and we should not alter it. delete directInstance; FileIOBase::SetDirectInstance(nullptr); if (pakInstance != directInstance) { delete pakInstance; FileIOBase::SetInstance(nullptr); } } m_env.pFileIO = nullptr; } ///////////////////////////////////////////////////////////////////////////////// bool CSystem::InitFileSystem_LoadEngineFolders(const SSystemInitParams&) { LoadConfiguration(m_systemConfigName.c_str()); AZ_Printf(AZ_TRACE_SYSTEM_WINDOW, "Loading system configuration from %s...", m_systemConfigName.c_str()); #if defined(AZ_PLATFORM_ANDROID) AZ::Android::Utils::SetLoadFilesToMemory(m_sys_load_files_to_memory->GetString()); #endif GetISystem()->SetConfigPlatform(GetDevicePlatform()); auto projectPath = AZ::Utils::GetProjectPath(); AZ_Printf(AZ_TRACE_SYSTEM_WINDOW, "Project Path: %s\n", projectPath.empty() ? "None specified" : projectPath.c_str()); auto projectName = AZ::Utils::GetProjectName(); AZ_Printf(AZ_TRACE_SYSTEM_WINDOW, "Project Name: %s\n", projectName.empty() ? "None specified" : projectName.c_str()); OpenBasicPaks(); // Load game-specific folder. LoadConfiguration("game.cfg"); // Load the client/sever-specific configuration static const char* g_additionalConfig = gEnv->IsDedicated() ? "server_cfg" : "client_cfg"; LoadConfiguration(g_additionalConfig, nullptr, false); // We do not use CVar groups on the consoles AddCVarGroupDirectory("Config/CVarGroups"); return (true); } ////////////////////////////////////////////////////////////////////////// bool CSystem::InitAudioSystem(const SSystemInitParams& initParams) { if (!Audio::Gem::AudioSystemGemRequestBus::HasHandlers()) { // AudioSystem Gem has not been enabled for this project. // This should not generate an error, but calling scope will warn. return false; } bool useRealAudioSystem = false; if (!initParams.bPreview && !m_bDedicatedServer && m_sys_audio_disable->GetIVal() == 0) { useRealAudioSystem = true; } bool result = false; if (useRealAudioSystem) { Audio::Gem::AudioSystemGemRequestBus::BroadcastResult(result, &Audio::Gem::AudioSystemGemRequestBus::Events::Initialize, &initParams); } else { Audio::Gem::AudioSystemGemRequestBus::BroadcastResult(result, &Audio::Gem::AudioSystemGemRequestBus::Events::Initialize, nullptr); } if (result) { AZ_Assert(Audio::AudioSystemRequestBus::HasHandlers(), "Initialization of the Audio System succeeded, but the Audio System EBus is not connected!\n"); } else { AZ_Error(AZ_TRACE_SYSTEM_WINDOW, result, "The Audio System did not initialize correctly!\n"); } return result; } ////////////////////////////////////////////////////////////////////////// bool CSystem::InitVTuneProfiler() { #ifdef PROFILE_WITH_VTUNE WIN_HMODULE hModule = LoadDLL("VTuneApi.dll"); if (!hModule) { return false; } VTPause = (VTuneFunction) CryGetProcAddress(hModule, "VTPause"); VTResume = (VTuneFunction) CryGetProcAddress(hModule, "VTResume"); if (!VTPause || !VTResume) { AZ_Assert(false, "VTune did not initialize correctly.") return false; } else { AZ_TracePrintf(AZ_TRACE_SYSTEM_WINDOW, "VTune API Initialized"); } #endif //PROFILE_WITH_VTUNE return true; } ////////////////////////////////////////////////////////////////////////// void CSystem::InitLocalization() { // Set the localization folder ICVar* pCVar = m_env.pConsole != 0 ? m_env.pConsole->GetCVar("sys_localization_folder") : 0; if (pCVar) { static_cast(m_env.pCryPak)->SetLocalizationFolder(g_cvars.sys_localization_folder->GetString()); } // Removed line that assigned language based on a #define if (m_pLocalizationManager == nullptr) { m_pLocalizationManager = new CLocalizedStringsManager(this); } // Platform-specific implementation of getting the system language ILocalizationManager::EPlatformIndependentLanguageID languageID = m_pLocalizationManager->GetSystemLanguage(); if (!m_pLocalizationManager->IsLanguageSupported(languageID)) { languageID = ILocalizationManager::EPlatformIndependentLanguageID::ePILID_English_US; } AZStd::string language = m_pLocalizationManager->LangNameFromPILID(languageID); m_pLocalizationManager->SetLanguage(language.c_str()); if (m_pLocalizationManager->GetLocalizationFormat() == 1) { AZStd::string translationsListXML = LOCALIZATION_TRANSLATIONS_LIST_FILE_NAME; m_pLocalizationManager->InitLocalizationData(translationsListXML.c_str()); m_pLocalizationManager->LoadAllLocalizationData(); } else { // if the language value cannot be found, let's default to the english pak OpenLanguagePak(language.c_str()); } if (auto console = AZ::Interface::Get(); console != nullptr) { AZ::CVarFixedString languageAudio; console->GetCvarValue("g_languageAudio", languageAudio); if (languageAudio.empty()) { console->PerformCommand(AZStd::string::format("g_languageAudio %s", language.c_str()).c_str()); } else { language.assign(languageAudio.data(), languageAudio.size()); } } OpenLanguageAudioPak(language.c_str()); } void CSystem::OpenBasicPaks() { static bool bBasicPaksLoaded = false; if (bBasicPaksLoaded) { return; } bBasicPaksLoaded = true; // open pak files constexpr AZStd::string_view paksFolder = "@products@/*.pak"; // (@products@ assumed) m_env.pCryPak->OpenPacks(paksFolder); InlineInitializationProcessing("CSystem::OpenBasicPaks OpenPacks( paksFolder.c_str() )"); ////////////////////////////////////////////////////////////////////////// // Open engine packs ////////////////////////////////////////////////////////////////////////// const char* const assetsDir = "@products@"; // After game paks to have same search order as with files on disk m_env.pCryPak->OpenPack(assetsDir, "engine.pak"); #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_15 #include AZ_RESTRICTED_FILE(SystemInit_cpp) #endif #ifdef AZ_PLATFORM_ANDROID // Load Android Obb files if available const char* obbStorage = AZ::Android::Utils::GetObbStoragePath(); AZStd::string mainObbPath = AZStd::move(AZStd::string::format("%s/%s", obbStorage, AZ::Android::Utils::GetObbFileName(true))); AZStd::string patchObbPath = AZStd::move(AZStd::string::format("%s/%s", obbStorage, AZ::Android::Utils::GetObbFileName(false))); m_env.pCryPak->OpenPack(assetsDir, mainObbPath.c_str()); m_env.pCryPak->OpenPack(assetsDir, patchObbPath.c_str()); #endif //AZ_PLATFORM_ANDROID InlineInitializationProcessing("CSystem::OpenBasicPaks OpenPacks( Engine... )"); } ////////////////////////////////////////////////////////////////////////// void CSystem::OpenLanguagePak(const char* sLanguage) { // Don't attempt to open a language PAK file if the game doesn't have a // loc folder configured. bool projUsesLocalization = false; LocalizationManagerRequestBus::BroadcastResult(projUsesLocalization, &LocalizationManagerRequestBus::Events::ProjectUsesLocalization); if (!projUsesLocalization) { return; } // Initialize languages. // Omit the trailing slash! AZStd::string sLocalizationFolder = PathUtil::GetLocalizationFolder(); // load xml pak with full filenames to perform wildcard searches. AZStd::string sLocalizedPath; GetLocalizedPath(sLanguage, sLocalizedPath); if (!m_env.pCryPak->OpenPacks({ sLocalizationFolder.c_str(), sLocalizationFolder.size() }, { sLocalizedPath.c_str(), sLocalizedPath.size() }, 0)) { // make sure the localized language is found - not really necessary, for TC AZ_Printf("Localization", "Localized language content(%s) not available or modified from the original installation.", sLanguage); } } ////////////////////////////////////////////////////////////////////////// void CSystem::OpenLanguageAudioPak([[maybe_unused]] const char* sLanguage) { // Don't attempt to open a language PAK file if the game doesn't have a // loc folder configured. bool projUsesLocalization = false; LocalizationManagerRequestBus::BroadcastResult(projUsesLocalization, &LocalizationManagerRequestBus::Events::ProjectUsesLocalization); if (!projUsesLocalization) { return; } // Initialize languages. // Omit the trailing slash! AZStd::string sLocalizationFolder(AZStd::string().assign(PathUtil::GetLocalizationFolder(), 0, PathUtil::GetLocalizationFolder().size() - 1)); if (!AZ::StringFunc::Equal(sLocalizationFolder, "Languages", false)) { sLocalizationFolder = "@products@"; } // load localized pak with crc32 filenames on consoles to save memory. AZStd::string sLocalizedPath = "loc.pak"; if (!m_env.pCryPak->OpenPacks(sLocalizationFolder.c_str(), sLocalizedPath.c_str())) { // make sure the localized language is found - not really necessary, for TC AZ_Error(AZ_TRACE_SYSTEM_WINDOW, false, "Localized language content(%s) not available or modified from the original installation.", sLanguage); } } AZStd::string GetUniqueLogFileName(AZStd::string logFileName) { AZStd::string logFileNamePrefix = logFileName; if ((logFileNamePrefix[0] != '@') && (AzFramework::StringFunc::Path::IsRelative(logFileNamePrefix.c_str()))) { logFileNamePrefix = "@log@/"; logFileNamePrefix += logFileName; } char resolvedLogFilePathBuffer[AZ_MAX_PATH_LEN] = { 0 }; AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(logFileNamePrefix.c_str(), resolvedLogFilePathBuffer, AZ_MAX_PATH_LEN); int instance = gEnv->pSystem->GetApplicationLogInstance(resolvedLogFilePathBuffer); if (instance == 0) { return logFileNamePrefix; } AZStd::string logFileExtension; size_t extensionIndex = logFileName.find_last_of('.'); if (extensionIndex != AZStd::string::npos) { logFileExtension = logFileName.substr(extensionIndex, logFileName.length() - extensionIndex); logFileNamePrefix = logFileName.substr(0, extensionIndex); } logFileName = AZStd::string::format("%s(%d)%s", logFileNamePrefix.c_str(), instance, logFileExtension.c_str()).c_str(); return logFileName; } class AzConsoleToCryConsoleBinder final { public: static void OnInvoke(IConsoleCmdArgs* args) { std::string command = args->GetCommandLine(); const size_t delim = command.find_first_of('='); if (delim != std::string::npos) { // All Cry executed cfg files will come in through this pathway in addition to regular commands // We strip out the = sign at this layer to maintain compatibility with cvars that use the '=' as a separator // Swap the '=' character for a space command[delim] = ' '; } AZ::Interface::Get()->PerformCommand(command.c_str(), AZ::ConsoleSilentMode::Silent, AZ::ConsoleInvokedFrom::CryBinding); } static void OnVarChanged(ICVar* cvar) { AZ::CVarFixedString command = AZ::CVarFixedString::format("%s %s", cvar->GetName(), cvar->GetString()); AZ::Interface::Get()->PerformCommand(command.c_str(), AZ::ConsoleSilentMode::Silent, AZ::ConsoleInvokedFrom::CryBinding); } static void Visit(AZ::ConsoleFunctorBase* functor) { if (gEnv->pConsole == nullptr) { AZ_Printf(AZ_TRACE_SYSTEM_WINDOW, "Cry console was NULL while attempting to register Az CVars and CFuncs.\n"); return; } int32_t cryFlags = VF_NET_SYNCED; if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::DontReplicate) != AZ::ConsoleFunctorFlags::Null) { cryFlags = VF_NULL; } if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::ServerOnly) != AZ::ConsoleFunctorFlags::Null) { cryFlags |= VF_DEDI_ONLY; } if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::ReadOnly) != AZ::ConsoleFunctorFlags::Null) { cryFlags |= VF_READONLY; } if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::IsCheat) != AZ::ConsoleFunctorFlags::Null) { cryFlags |= VF_CHEAT; } if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::IsInvisible) != AZ::ConsoleFunctorFlags::Null) { cryFlags |= VF_INVISIBLE; } if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::IsDeprecated) != AZ::ConsoleFunctorFlags::Null) { cryFlags |= VF_DEPRECATED; } if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::NeedsReload) != AZ::ConsoleFunctorFlags::Null) { cryFlags |= VF_REQUIRE_APP_RESTART; } if ((functor->GetFlags() & AZ::ConsoleFunctorFlags::AllowClientSet) != AZ::ConsoleFunctorFlags::Null) { cryFlags |= VF_DEV_ONLY; } gEnv->pConsole->RemoveCommand(functor->GetName()); if (functor->GetTypeId() != AZ::TypeId::CreateNull()) { AZ::CVarFixedString value; functor->GetValue(value); gEnv->pConsole->RegisterString(functor->GetName(), value.c_str(), cryFlags, functor->GetDesc(), AzConsoleToCryConsoleBinder::OnVarChanged); } else { gEnv->pConsole->AddCommand(functor->GetName(), AzConsoleToCryConsoleBinder::OnInvoke, cryFlags, functor->GetDesc()); } } using CommandRegisteredHandler = AZ::IConsole::ConsoleCommandRegisteredEvent::Handler; static inline CommandRegisteredHandler s_commandRegisteredHandler = CommandRegisteredHandler([](AZ::ConsoleFunctorBase* functor) { Visit(functor); }); }; // System initialization ///////////////////////////////////////////////////////////////////////////////// // INIT ///////////////////////////////////////////////////////////////////////////////// bool CSystem::Init(const SSystemInitParams& startupParams) { #if AZ_TRAIT_USE_CRY_SIGNAL_HANDLER signal(SIGSEGV, CryEngineSignalHandler); signal(SIGTRAP, CryEngineSignalHandler); signal(SIGILL, CryEngineSignalHandler); #endif // AZ_TRAIT_USE_CRY_SIGNAL_HANDLER // Temporary Fix for an issue accessing gEnv from this object instance. The gEnv is not resolving to the // global gEnv, instead its resolving an some uninitialized gEnv elsewhere (NULL). Since gEnv is // initialized to this instance's SSystemGlobalEnvironment (m_env), we will force set it again here // to m_env if (!gEnv) { gEnv = &m_env; } SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_INIT); gEnv->mMainThreadId = GetCurrentThreadId(); //Set this ASAP on startup InlineInitializationProcessing("CSystem::Init start"); m_env.bNoAssertDialog = false; m_bNoCrashDialog = gEnv->IsDedicated(); if (startupParams.bUnattendedMode) { m_bNoCrashDialog = true; m_env.bNoAssertDialog = true; //this also suppresses CryMessageBox g_cvars.sys_no_crash_dialog = true; } #if defined(AZ_PLATFORM_LINUX) // Linux is all console for now and so no room for dialog boxes! m_env.bNoAssertDialog = true; #endif m_pCmdLine = new CCmdLine(startupParams.szSystemCmdLine); AZCoreLogSink::Connect(); // Registers all AZ Console Variables functors specified within CrySystem if (auto azConsole = AZ::Interface::Get(); azConsole) { azConsole->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); } if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry) { AZ::SettingsRegistryInterface::FixedValueString assetPlatform; if (!AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, assetPlatform, AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "assets")) { assetPlatform = AzFramework::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME); AZ_Warning(AZ_TRACE_SYSTEM_WINDOW, false, R"(A valid asset platform is missing in "%s/assets" key in the SettingsRegistry.)""\n" R"(This typically done by setting the "assets" field within a .setreg file)""\n" R"(A fallback of %s will be used.)", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, assetPlatform.c_str()); } m_systemConfigName = "system_" AZ_TRAIT_OS_PLATFORM_CODENAME_LOWER "_"; m_systemConfigName += assetPlatform.c_str(); m_systemConfigName += ".cfg"; } #if defined(WIN32) || defined(WIN64) // check OS version - we only want to run on XP or higher - talk to Martin Mittring if you want to change this { OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); AZ_PUSH_DISABLE_WARNING(4996, "-Wunknown-warning-option") GetVersionExW(&osvi); AZ_POP_DISABLE_WARNING bool bIsWindowsXPorLater = osvi.dwMajorVersion > 5 || (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion >= 1); if (!bIsWindowsXPorLater) { AZ_Error(AZ_TRACE_SYSTEM_WINDOW, false, "Open 3D Engine requires an OS version of Windows XP or later."); return false; } } #endif // Get file version information. QueryVersionInfo(); DetectGameFolderAccessRights(); m_bEditor = startupParams.bEditor; m_bPreviewMode = startupParams.bPreview; m_bTestMode = startupParams.bTestMode; m_pUserCallback = startupParams.pUserCallback; m_bDedicatedServer = startupParams.bDedicatedServer; m_currentLanguageAudio = ""; #if !defined(CONSOLE) m_env.SetIsEditor(m_bEditor); m_env.SetIsEditorGameMode(false); m_env.SetIsEditorSimulationMode(false); #endif m_env.SetToolMode(startupParams.bToolMode); if (m_bEditor) { m_bInDevMode = true; } if (!gEnv->IsDedicated()) { const ICmdLineArg* crashdialog = m_pCmdLine->FindArg(eCLAT_Post, "sys_no_crash_dialog"); if (crashdialog) { m_bNoCrashDialog = true; } } #if !defined(_RELEASE) if (!m_bDedicatedServer) { const ICmdLineArg* dedicated = m_pCmdLine->FindArg(eCLAT_Pre, "dedicated"); if (dedicated) { m_bDedicatedServer = true; } } #endif // !defined(_RELEASE) #if !defined(CONSOLE) gEnv->SetIsDedicated(m_bDedicatedServer); #endif { EBUS_EVENT(CrySystemEventBus, OnCrySystemPreInitialize, *this, startupParams); ////////////////////////////////////////////////////////////////////////// // File system, must be very early ////////////////////////////////////////////////////////////////////////// if (!InitFileSystem()) { return false; } ////////////////////////////////////////////////////////////////////////// InlineInitializationProcessing("CSystem::Init InitFileSystem"); m_missingAssetLogger = AZStd::make_unique(); ////////////////////////////////////////////////////////////////////////// // Logging is only available after file system initialization. ////////////////////////////////////////////////////////////////////////// if (!startupParams.pLog) { m_env.pLog = new CLog(this); if (startupParams.pLogCallback) { m_env.pLog->AddCallback(startupParams.pLogCallback); } const ICmdLineArg* logfile = m_pCmdLine->FindArg(eCLAT_Pre, "logfile"); //see if the user specified a log name, if so use it if (logfile && strlen(logfile->GetValue()) > 0) { m_env.pLog->SetFileName(logfile->GetValue(), startupParams.autoBackupLogs); } else if (startupParams.sLogFileName) //otherwise see if the startup params has a log file name, if so use it { const AZStd::string sUniqueLogFileName = GetUniqueLogFileName(startupParams.sLogFileName); m_env.pLog->SetFileName(sUniqueLogFileName.c_str(), startupParams.autoBackupLogs); } else//use the default log name { m_env.pLog->SetFileName(DEFAULT_LOG_FILENAME, startupParams.autoBackupLogs); } } else { m_env.pLog = startupParams.pLog; } // The log backup system expects the version number to be the first line of the log // so we log this immediately after setting the log filename LogVersion(); bool devModeEnable = true; #if defined(_RELEASE) // disable devmode by default in release builds outside the editor devModeEnable = m_bEditor; #endif // disable devmode in launcher if someone really wants to (even in non release builds) if (!m_bEditor && m_pCmdLine->FindArg(eCLAT_Pre, "nodevmode")) { devModeEnable = false; } SetDevMode(devModeEnable); ////////////////////////////////////////////////////////////////////////// // CREATE CONSOLE ////////////////////////////////////////////////////////////////////////// if (!startupParams.bSkipConsole) { m_env.pConsole = new CXConsole; if (startupParams.pPrintSync) { m_env.pConsole->AddOutputPrintSink(startupParams.pPrintSync); } } ////////////////////////////////////////////////////////////////////////// if (m_pUserCallback) { m_pUserCallback->OnInit(this); } m_env.pLog->RegisterConsoleVariables(); GetIRemoteConsole()->RegisterConsoleVariables(); if (!startupParams.bSkipConsole) { // Register system console variables. CreateSystemVars(); // Register Audio-related system CVars CreateAudioVars(); // Register any AZ CVar commands created above with the AZ Console system. AZ::ConsoleFunctorBase*& deferredHead = AZ::ConsoleFunctorBase::GetDeferredHead(); AZ::Interface::Get()->LinkDeferredFunctors(deferredHead); // Callback if (m_pUserCallback && m_env.pConsole) { m_pUserCallback->OnConsoleCreated(m_env.pConsole); } // Let listeners know its safe to register cvars EBUS_EVENT(CrySystemEventBus, OnCrySystemCVarRegistry); } // Set this as soon as the system cvars got initialized. static_cast(m_env.pCryPak)->SetLocalizationFolder(g_cvars.sys_localization_folder->GetString()); InlineInitializationProcessing("CSystem::Init Create console"); InitFileSystem_LoadEngineFolders(startupParams); #if !defined(RELEASE) || defined(RELEASE_LOGGING) // now that the system cfgs have been loaded, we can start the remote console GetIRemoteConsole()->Update(); #endif InlineInitializationProcessing("CSystem::Init Load Engine Folders"); ////////////////////////////////////////////////////////////////////////// //Load config files ////////////////////////////////////////////////////////////////////////// int curSpecVal = 0; ICVar* pSysSpecCVar = gEnv->pConsole->GetCVar("r_GraphicsQuality"); if (gEnv->pSystem->IsDevMode()) { if (pSysSpecCVar && pSysSpecCVar->GetFlags() & VF_WASINCONFIG) { curSpecVal = pSysSpecCVar->GetIVal(); pSysSpecCVar->SetFlags(pSysSpecCVar->GetFlags() | VF_SYSSPEC_OVERWRITE); } } // tools may not interact with @user@ if (!gEnv->IsInToolMode()) { if (m_pCmdLine->FindArg(eCLAT_Pre, "ResetProfile") == 0) { LoadConfiguration("@user@/game.cfg", 0, false); } } // If sys spec variable was specified, is not 0, and we are in devmode restore the value from before loading game.cfg // This enables setting of a specific sys_spec outside menu and game.cfg if (gEnv->pSystem->IsDevMode()) { if (pSysSpecCVar && curSpecVal && curSpecVal != pSysSpecCVar->GetIVal()) { pSysSpecCVar->Set(curSpecVal); } } { // We have to load this file again since first time we did it without devmode LoadConfiguration(m_systemConfigName.c_str()); // Optional user defined overrides LoadConfiguration("user.cfg"); #if defined(ENABLE_STATS_AGENT) if (m_pCmdLine->FindArg(eCLAT_Pre, "useamblecfg")) { LoadConfiguration("amble.cfg"); } #endif } ////////////////////////////////////////////////////////////////////////// if (g_cvars.sys_asserts == 0) { gEnv->bIgnoreAllAsserts = true; } if (g_cvars.sys_asserts == 2) { gEnv->bNoAssertDialog = true; } LogBuildInfo(); InlineInitializationProcessing("CSystem::Init LoadConfigurations"); #ifdef WIN32 if (g_cvars.sys_WER) { SetUnhandledExceptionFilter(CryEngineExceptionFilterWER); } #endif ////////////////////////////////////////////////////////////////////////// // Localization ////////////////////////////////////////////////////////////////////////// { InitLocalization(); } InlineInitializationProcessing("CSystem::Init InitLocalizations"); ////////////////////////////////////////////////////////////////////////// // Open basic pak files after intro movie playback started ////////////////////////////////////////////////////////////////////////// OpenBasicPaks(); ////////////////////////////////////////////////////////////////////////// // AUDIO ////////////////////////////////////////////////////////////////////////// { if (InitAudioSystem(startupParams)) { // Pump the Log - Audio initialization happened on a non-main thread, there may be log messages queued up. gEnv->pLog->Update(); } else { // Failure to initialize audio system is no longer a fatal or an error. A warning is sufficient. AZ_Warning(AZ_TRACE_SYSTEM_WINDOW, false, "