Regenerating configuration files?

Hi there,

Recently we encountered an issue where the Input.ini in the Saved/Config/ directory had the ActionMappings written out to it and any ActionMappings in DefaultInput.ini were no longer included when new ones were added or old ones changed.

Tracking this through the ConfigCacheIni.cpp that holds all the configuration initialization code, it appears that unless certain special flags are set or certain conditions are met, once the Input.ini is written, it won’t pick up on any new ActionMappings. So if we later add new mappings to DefaultInput.ini for new keys then it won’t be picked up.

This is the code I’ve been looking at:

	// Don't try to load any generated files from disk in cooked builds. We will always use the re-generated INIs.
	if (!FPlatformProperties::RequiresCookedData() || bAllowGeneratedINIs)
	{
		// We need to check if the user is using the version of the config system which had the entire contents of the coalesced
		// Source ini hierarchy output, if so we need to update, as it will cause issues with the new way we handle saved config files.
		bool bIsLegacyConfigSystem = false;
		for ( TMap<FString,FConfigSection>::TConstIterator It(DestConfigFile); It; ++It )
		{
			FString SectionName = It.Key();
			if( SectionName == TEXT("IniVersion") || SectionName == TEXT("Engine.Engine") )
			{
				bIsLegacyConfigSystem = true;
				UE_LOG( LogInit, Warning, TEXT( "%s is out of date. It will be regenerated." ), *FPaths::ConvertRelativePathToFull(DestIniFilename) );
				break;
			}
		}

		// Regenerate the ini file?
		if( bIsLegacyConfigSystem || FParse::Param(FCommandLine::Get(),TEXT("REGENERATEINIS")) == true )
		{
			bForceRegenerate = true;
		}
		else if( FParse::Param(FCommandLine::Get(),TEXT("NOAUTOINIUPDATE")) )
		{
			// Flag indicating whether the user has requested 'Yes/No To All'.
			static int32 GIniYesNoToAll = -1;
			// Make sure GIniYesNoToAll's 'uninitialized' value is kosher.
			static_assert(EAppReturnType::YesAll != -1, "EAppReturnType::YesAll must not be -1.");
			static_assert(EAppReturnType::NoAll != -1, "EAppReturnType::NoAll must not be -1.");

			// The file exists but is different.
			// Prompt the user if they haven't already responded with a 'Yes/No To All' answer.
			uint32 YesNoToAll;
			if( GIniYesNoToAll != EAppReturnType::YesAll && GIniYesNoToAll != EAppReturnType::NoAll )
			{
				YesNoToAll = FMessageDialog::Open(EAppMsgType::YesNoYesAllNoAll, FText::Format( NSLOCTEXT("Core", "IniFileOutOfDate", "Your ini ({0}) file is outdated. Do you want to automatically update it saving the previous version? Not doing so might cause crashes!"), FText::FromString( DestIniFilename ) ) );
				// Record whether the user responded with a 'Yes/No To All' answer.
				if ( YesNoToAll == EAppReturnType::YesAll || YesNoToAll == EAppReturnType::NoAll )
				{
					GIniYesNoToAll = YesNoToAll;
				}
			}
			else
			{
				// The user has already responded with a 'Yes/No To All' answer, so note it 
				// in the output arg so that calling code can operate on its value.
				YesNoToAll = GIniYesNoToAll;
			}
			// Regenerate the file if approved by the user.
			bShouldUpdate = (YesNoToAll == EAppReturnType::Yes) || (YesNoToAll == EAppReturnType::YesAll);
		}
		else
		{
			bShouldUpdate = true;
		}
	}
	
	// Regenerate the file.
	if( bForceRegenerate )
	{
		bResult = LoadIniFileHierarchy(SourceIniHierarchy, DestConfigFile, bUseHierarchyCache);
		DestConfigFile.SourceConfigFile = new FConfigFile( DestConfigFile );

		// mark it as dirty (caller may want to save)
		DestConfigFile.Dirty = true;
	}
	else if( bShouldUpdate )
	{
		// Merge the .ini files by copying over properties that exist in the default .ini but are
		// missing from the generated .ini
		// NOTE: Most of the time there won't be any properties to add here, since LoadAnIniFile will
		//		 combine properties in the Default .ini with those in the Project .ini
		DestConfigFile.AddMissingProperties(*DestConfigFile.SourceConfigFile);

		// mark it as dirty (caller may want to save)
		DestConfigFile.Dirty = true;
	}

From testing and the above code, this seems to happen when a shipping build is being used; though I’m not 100% certain of this. Otherwise the Input.ini in Saved/Config is left empty and the mappings are just collected in memory and not written out.

So my question is more to see if there’s any general information out there about this behaviour and if it’s expected.

Additionally, if we wanted to temporarily disable this behaviour in shipping builds I’ve found that there is a command line argument called -regenerateinis (as seen in the above code) that will ignore the Saved/Config files and always collect configuration information from source.

Is there a way to enable this behaviour from our game code or will it be necessary to edit the engine code to add this to the command line argument before the configuration system is initialized? i.e. is there a game specific code entry point to add command line arguments before the main loop starts?

We have also run up against this. The users already have Input.ini written out with “ActionMappings” , which causes our changes to DefaultInput.ini to be ignored.