Posted onEdited onInUnrealEngineViews: Symbols count in article: 2.1kReading time ≈5 mins.
The UE4 provides a mature configuration mechanism for INI files, and the engine uses ini as configuration files for both the engine and projects. This article will briefly analyze the loading of GConfig in the engine.
There are a set of global ini files defined in UE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Runtime/Core/Private/Misc/CoreGlobals.cpp FString GEngineIni; /* Engine ini filename */
/** Editor ini file locations - stored per engine version (shared across all projects). Migrated between versions on first run. */ FString GEditorIni; /* Editor ini filename */ FString GEditorKeyBindingsIni; /* Editor Key Bindings ini file */ FString GEditorLayoutIni; /* Editor UI Layout ini filename */ FString GEditorSettingsIni; /* Editor Settings ini filename */
/** Editor per-project ini files - stored per project. */ FString GEditorPerProjectIni; /* Editor User Settings ini filename */
FString GCompatIni; FString GLightmassIni; /* Lightmass settings ini filename */ FString GScalabilityIni; /* Scalability settings ini filename */ FString GHardwareIni; /* Hardware ini filename */ FString GInputIni; /* Input ini filename */ FString GGameIni; /* Game ini filename */ FString GGameUserSettingsIni; /* User Game Settings ini filename */
However, there is no direct hard-coded specification of where the ini files are located. The loading process occurs in FEngineLoop::AppInit (LaunchEngineLoop.cpp) by calling FConfigCacheIni::InitializeConfigSystem, defined in ConfigCacheIni.cpp:
// -------------------------------- // # FConfigCacheIni::InitializeConfigSystem declaration /** * Creates GConfig, loads the standard global ini files (Engine, Editor, etc), * fills out GEngineIni, etc. and marks GConfig as ready for use */ staticvoidInitializeConfigSystem(); // -------------------------------- voidFConfigCacheIni::InitializeConfigSystem() { // Perform any upgrade we need before we load any configuration files FConfigManifest::UpgradeFromPreviousVersions();
// load the main .ini files (unless we're running a program or a gameless UE4Editor.exe, DefaultEngine.ini is required). constbool bIsGamelessExe = !FApp::HasProjectName(); constbool bDefaultEngineIniRequired = !bIsGamelessExe && (GIsGameAgnosticExe || FApp::IsProjectNameEmpty()); bool bEngineConfigCreated = FConfigCacheIni::LoadGlobalIniFile(GEngineIni, TEXT("Engine"), nullptr, bDefaultEngineIniRequired);
if ( !bIsGamelessExe ) { // Now check and see if our game is correct if this is a game agnostic binary if (GIsGameAgnosticExe && !bEngineConfigCreated) { const FText AbsolutePath = FText::FromString( IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FPaths::GetPath(GEngineIni)) ); //@todo this is too early to localize const FText Message = FText::Format( NSLOCTEXT("Core", "FirstCmdArgMustBeGameName", "'{0}' must exist and contain a DefaultEngine.ini."), AbsolutePath ); if (!GIsBuildMachine) { FMessageDialog::Open(EAppMsgType::Ok, Message); } FApp::SetProjectName(TEXT("")); // this disables part of the crash reporter to avoid writing log files to a bogus directory if (!GIsBuildMachine) { exit(1); } UE_LOG(LogInit, Fatal,TEXT("%s"), *Message.ToString()); } }
FConfigCacheIni::LoadGlobalIniFile(GGameIni, TEXT("Game")); FConfigCacheIni::LoadGlobalIniFile(GInputIni, TEXT("Input")); #if WITH_EDITOR // load some editor specific .ini files
// Upgrade editor user settings before loading the editor per project user settings FConfigManifest::MigrateEditorUserSettings(); FConfigCacheIni::LoadGlobalIniFile(GEditorPerProjectIni, TEXT("EditorPerProjectUserSettings"));
#endif #if PLATFORM_DESKTOP // load some desktop only .ini files FConfigCacheIni::LoadGlobalIniFile(GCompatIni, TEXT("Compat")); FConfigCacheIni::LoadGlobalIniFile(GLightmassIni, TEXT("Lightmass")); #endif
// Load scalability settings. FConfigCacheIni::LoadGlobalIniFile(GScalabilityIni, TEXT("Scalability")); // Load driver blacklist FConfigCacheIni::LoadGlobalIniFile(GHardwareIni, TEXT("Hardware")); // Load user game settings .ini, allowing merging. This also updates the user .ini if necessary. FConfigCacheIni::LoadGlobalIniFile(GGameUserSettingsIni, TEXT("GameUserSettings"));
// now we can make use of GConfig GConfig->bIsReadyForUse = true; FCoreDelegates::ConfigReadyForUse.Broadcast(); }
// -------------------------------- // # FConfigCacheIni::LoadGlobalIniFile declaration /** * Loads and generates a destination ini file and adds it to GConfig: * - Looking on commandline for override source/dest .ini filenames * - Generating the name for the engine to refer to the ini * - Loading a source .ini file hierarchy * - Filling out an FConfigFile * - Save the generated ini * - Adds the FConfigFile to GConfig * * @param FinalIniFilename The output name of the generated .ini file (in Game\Saved\Config) * @param BaseIniName The "base" ini name, with no extension (ie, Engine, Game, etc) * @param Platform The platform to load the .ini for (if NULL, uses current) * @param bForceReload If true, the destination .in will be regenerated from the source, otherwise this will only process if the dest isn't in GConfig * @param bRequireDefaultIni If true, the Default*.ini file is required to exist when generating the final ini file. * @param bAllowGeneratedIniWhenCooked If true, the engine will attempt to load the generated/user INI file when loading cooked games * @param GeneratedConfigDir The location where generated config files are made. * @return true if the final ini was created successfully. */ staticboolLoadGlobalIniFile(FString& FinalIniFilename, const TCHAR* BaseIniName, const TCHAR* Platform=NULL, bool bForceReload=false, bool bRequireDefaultIni=false, bool bAllowGeneratedIniWhenCooked=true, const TCHAR* GeneratedConfigDir = *FPaths::GeneratedConfigDir()); // --------------------------------
boolFConfigCacheIni::LoadGlobalIniFile(FString& FinalIniFilename, const TCHAR* BaseIniName, const TCHAR* Platform, bool bForceReload, bool bRequireDefaultIni, bool bAllowGeneratedIniWhenCooked, const TCHAR* GeneratedConfigDir) { // figure out where the end ini file is FinalIniFilename = GetDestIniFilename(BaseIniName, Platform, GeneratedConfigDir); // Start the loading process for the remote config file when appropriate if (FRemoteConfig::Get()->ShouldReadRemoteFile(*FinalIniFilename)) { FRemoteConfig::Get()->Read(*FinalIniFilename, BaseIniName); }
FRemoteConfigAsyncIOInfo* RemoteInfo = FRemoteConfig::Get()->FindConfig(*FinalIniFilename); if (RemoteInfo && (!RemoteInfo->bWasProcessed || !FRemoteConfig::Get()->IsFinished(*FinalIniFilename))) { // Defer processing this remote config file to until it has finished its IO operation returnfalse; }
// need to check to see if the file already exists in the GConfigManager's cache // if it does exist then we are done, nothing else to do if (!bForceReload && GConfig->FindConfigFile(*FinalIniFilename) != nullptr) { //UE_LOG(LogConfig, Log, TEXT( "Request to load a config file that was already loaded: %s" ), GeneratedIniFile ); returntrue; }
// make a new entry in GConfig (overwriting what's already there) FConfigFile& NewConfigFile = GConfig->Add(FinalIniFilename, FConfigFile());
// -------------------------------- // # FConfigCacheIni::LoadLocalIniFile declaration /** * Load an ini file directly into an FConfigFile, and nothing is written to GConfig or disk. * The passed in .ini name can be a "base" (Engine, Game) which will be modified by platform and/or commandline override, * or it can be a full ini filename (ie WrangleContent) loaded from the Source config directory * * @param ConfigFile The output object to fill * @param IniName Either a Base ini name (Engine) or a full ini name (WrangleContent). NO PATH OR EXTENSION SHOULD BE USED! * @param bIsBaseIniName true if IniName is a Base name, which can be overridden on commandline, etc. * @param Platform The platform to use for Base ini names, NULL means to use the current platform * @param bForceReload force reload the ini file from disk this is required if you make changes to the ini file not using the config system as the hierarchy cache will not be updated in this case * @return true if the ini file was loaded successfully */ staticboolLoadLocalIniFile(FConfigFile& ConfigFile, const TCHAR* IniName, bool bIsBaseIniName, const TCHAR* Platform=NULL, bool bForceReload=false); // -------------------------------- boolFConfigCacheIni::LoadLocalIniFile(FConfigFile& ConfigFile, const TCHAR* IniName, bool bIsBaseIniName, const TCHAR* Platform, bool bForceReload ) { DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FConfigCacheIni::LoadLocalIniFile" ), STAT_FConfigCacheIni_LoadLocalIniFile, STATGROUP_LoadTime );
if (bIsBaseIniName) { FConfigFile* BaseConfig = GConfig->FindConfigFileWithBaseName(IniName); // If base ini, try to use an existing GConfig file to set the config directories instead of assuming defaults
if (BaseConfig) { FIniFilename* EngineFilename = BaseConfig->SourceIniHierarchy.Find(EConfigFileHierarchy::EngineDirBase); if (EngineFilename) { EngineConfigDir = FPaths::GetPath(EngineFilename->Filename) + TEXT("/"); }
// -------------------------------- // # FConfigCacheIni::LoadExternalIniFile declaration /** * Load an ini file directly into an FConfigFile from the specified config folders, optionally writing to disk. * The passed in .ini name can be a "base" (Engine, Game) which will be modified by platform and/or commandline override, * or it can be a full ini filename (ie WrangleContent) loaded from the Source config directory * * @param ConfigFile The output object to fill * @param IniName Either a Base ini name (Engine) or a full ini name (WrangleContent). NO PATH OR EXTENSION SHOULD BE USED! * @param EngineConfigDir Engine config directory. * @param SourceConfigDir Game config directory. * @param bIsBaseIniName true if IniName is a Base name, which can be overridden on commandline, etc. * @param Platform The platform to use for Base ini names * @param bForceReload force reload the ini file from disk this is required if you make changes to the ini file not using the config system as the hierarchy cache will not be updated in this case * @param bWriteDestIni write out a destination ini file to the Saved folder, only valid if bIsBaseIniName is true * @param bAllowGeneratedIniWhenCooked If true, the engine will attempt to load the generated/user INI file when loading cooked games * @param GeneratedConfigDir The location where generated config files are made. * @return true if the ini file was loaded successfully */ staticboolLoadExternalIniFile(FConfigFile& ConfigFile, const TCHAR* IniName, const TCHAR* EngineConfigDir, const TCHAR* SourceConfigDir, bool bIsBaseIniName, const TCHAR* Platform=NULL, bool bForceReload=false, bool bWriteDestIni=false, bool bAllowGeneratedIniWhenCooked = true, const TCHAR* GeneratedConfigDir = *FPaths::GeneratedConfigDir()); // -------------------------------- boolFConfigCacheIni::LoadExternalIniFile(FConfigFile& ConfigFile, const TCHAR* IniName, const TCHAR* EngineConfigDir, const TCHAR* SourceConfigDir, bool bIsBaseIniName, const TCHAR* Platform, bool bForceReload, bool bWriteDestIni, bool bAllowGeneratedIniWhenCooked, const TCHAR* GeneratedConfigDir) { // if bIsBaseIniName is false, that means the .ini is a ready-to-go .ini file, and just needs to be loaded into the FConfigFile if (!bIsBaseIniName) { // generate path to the .ini file (not a Default ini, IniName is the complete name of the file, without path) FString SourceIniFilename = FString::Printf(TEXT("%s/%s.ini"), SourceConfigDir, IniName);
// load the .ini file straight up LoadAnIniFile(*SourceIniFilename, ConfigFile);
if ( bForceReload ) { ClearHierarchyCache( IniName ); }
// Keep a record of the original settings ConfigFile.SourceConfigFile = newFConfigFile();
// now generate and make sure it's up to date (using IniName as a Base for an ini filename) constbool bAllowGeneratedINIs = true; bool bNeedsWrite = GenerateDestIniFile(ConfigFile, DestIniFilename, ConfigFile.SourceIniHierarchy, bAllowGeneratedIniWhenCooked, true);
ConfigFile.Name = IniName;
// don't write anything to disk in cooked builds - we will always use re-generated INI files anyway. if (bWriteDestIni && (!FPlatformProperties::RequiresCookedData() || bAllowGeneratedIniWhenCooked) // We shouldn't save config files when in multiprocess mode, // otherwise we get file contention in XGE shader builds. && !FParse::Param(FCommandLine::Get(), TEXT("Multiprocess"))) { // Check the config system for any changes made to defaults and propagate through to the saved. ConfigFile.ProcessSourceAndCheckAgainstBackup();
if (bNeedsWrite) { // if it was dirtied during the above function, save it out now ConfigFile.Write(DestIniFilename); } } }
// GenerateDestIniFile returns true if nothing is loaded, so check if we actually loaded something return ConfigFile.Num() > 0; }
The most important part of this function is the call to GetSourceIniHierarchyFilenames, which collects all ini files of the current baseName under the Engine and project.
This is why we see many ini files in GConfig that are in Saved/Config and contain empty content, yet how do we retrieve the settings from the project? This is because UE merges multiple ini files:
The content of these ini files will all be loaded.
This part is implemented in FConfigFile::AddStaticLayersToHierarchy, which traverses all ini files in GConfigLayers with rules:
structFConfigLayer { // Used by the editor to display in the ini-editor const TCHAR* EditorName; // Path to the ini file (with variables) const TCHAR* Path; // Special flag EConfigLayerFlags Flag;
} GConfigLayers[] = { /************************************************** **** CRITICAL NOTES **** If you change this array, you need to also change EnumerateConfigFileLocations() in ConfigHierarchy.cs!!! **** And maybe UObject::GetDefaultConfigFilename(), UObject::GetGlobalUserConfigFilename() **************************************************/
Note: The last element of GConfigLayers has the EConfigLayerFlags::GenerateCacheKey FLAG, when processing, it concatenates all ini paths as a Key:
FConfigFile::AddStaticLayersToHierarchy
1 2 3 4 5 6
// add this to the list! SourceIniHierarchy.AddStaticLayer( FIniFilename(PlatformPath, bIsRequired, bGenerateCacheKey ? GenerateHierarchyCacheKey(SourceIniHierarchy, PlatformPath, InBaseIniName) : FString(TEXT(""))), LayerIndex, ExpansionIndex, PlatformIndex);
Finally, the list of paths for all loadable ini files will be executed in the GenerateDestIniFile function during the actual loading behavior (for the Engine example):
The loading stack of ini files:
Reading will occur in the order of the ini path list, indicating that the priorities of ini files are also according to the loading order.
/** * Calculates the name of a dest (generated) .ini file for a given base (ie Engine, Game, etc) * * @param IniBaseName Base name of the .ini (Engine, Game) * @param PlatformName Name of the platform to get the .ini path for (nullptr means to use the current platform) * @param GeneratedConfigDir The base folder that will contain the generated config files. * * @return Standardized .ini filename */ static FString GetDestIniFilename(const TCHAR* BaseIniName, const TCHAR* PlatformName, const TCHAR* GeneratedConfigDir) { // figure out what to look for on the commandline for an override FString CommandLineSwitch = FString::Printf(TEXT("%sINI="), BaseIniName); // if it's not found on the commandline, then generate it FString IniFilename; if (FParse::Value(FCommandLine::Get(), *CommandLineSwitch, IniFilename) == false) { FString Name(PlatformName ? PlatformName : ANSI_TO_TCHAR(FPlatformProperties::PlatformName()));
FString BaseIniNameString = BaseIniName; if (BaseIniNameString.Contains(GeneratedConfigDir)) { IniFilename = BaseIniNameString; } else { // put it all together IniFilename = FString::Printf(TEXT("%s%s/%s.ini"), GeneratedConfigDir, *Name, BaseIniName); } }
That is, the global G*Ini files read when the project starts on the Windows platform are all under Saved/Config/Windows.ini. Moreover, in the implementation of GetDestIniFilename, it can also be seen that the configuration of G*Ini can be passed in through the CommandLine, allowing the default .ini under Saved/Config/Platform to be overridden:
1 2
// Using the specified Engine.ini UE4Editor.exe uprojectPath -EngineINI="D:\\CustomEngine.ini"