NEED HELP: Creating Custom Game User Settings (CPP)

So I’m trying to create my own Custom Game User Settings.
Cuz Unreal’s default one’s quite an spaghetti code. And I tried to simplify it, using my own structs, and Applying Settings based of Category.

I gotta ask:

  1. Can I make my custom name for the ini of Custom Game User Settings (CndGameUserSettings.ini), and save my Settings there?

  2. What should happen first, when it’s being initialized?
    Like… Validating Settings first, then Loading Them (Applying Defaults)?

Should fetching all supported resolutions and all that be initialized when being initialized too, to determine max values?

  1. If I set the value using this for ex.:
    Does CndGameUserSettings.ini, if it doesn’t exist, gets created?

GConfig->SetInt(*SectionName, *KeyName, Cnd_Version, GGameUserSettingsIni);

The short answer is:

  1. Yes you can.
  2. Ini files are loaded on the startup of Unreal Engine. I think they are validated on assignment.
  3. No. If you want to have additional .ini files you have to create them yourself. (not only that but I think you need to have your categories added inside)

Here is the documentation if you need more in-depth answers.

P.S. You can also check this Config Files, Read & Write to Config Files | Unreal Engine Community Wiki for particular exmples.

[Unreal Engine 5, File, Config, CSV, Json, Plugin, C++]
(https://www.youtube.com/watch?v=bUDyxA5PZdQ)

So… how I can create this custom INI?

With this?

// INITIALIZER CONSTRUCTOR (DEFAULT)
UCndSysSave_UserSettings::UCndSysSave_UserSettings()
{

GGameUserSettingsIni = FPaths::ProjectSavedDir() + TEXT("CndGameUserSettings.ini");

CreateCustomIniFile():

}
void CreateCustomIniFile()
{

	// if (FileExists) {} else {}

	FString CustomFileName = FPaths::ProjectConfigDir() + TEXT("CndGameUserSettings.ini");
	FString SectionName = TEXT("CustomSettings");
	FString KeyName = TEXT("Version");
	int32 Cnd_Version_Initial = 1;

GConfig->SetInt(*SectionName, *KeyName, Cnd_Version_Initial, *CustomFileName);

GConfig->Flush(false, *CustomFileName);

}

Oh, by “create them yourself” I meant manually creating a .txt file and changing its name to .ini. (also adding your categories in square brackets and even your initial values)

I guess you can create it runtime but I don’t see why would you you want do that.

Did some thinkering… and I think I set it right.

UCndSysSave_UserSettings::UCndSysSave_UserSettings()
{

	Cnd_SetupIniFile();

	BPF_Init_GetMaxSettingsValues();

}
void Cnd_SetupIniFile()
{

	// Set a custom name for the user settings ini file
	FString Cnd_US_FilePath = FPaths::ProjectConfigDir() + TEXT("CndGameUserSettings.ini");	
	GGameUserSettingsIni = Cnd_US_FilePath;

//	UE_LOG(LogTemp, Log, TEXT("Game User Settings will be saved in: %s"), *GGameUserSettingsIni);
	
	bool FileExists = FPlatformFileManager::Get().GetPlatformFile().FileExists(*Cnd_US_FilePath);

	if (!FileExists)
	{

		FString SectionName = TEXT("CndGeneral");
		FString KeyName = TEXT("Version");
		int32 Cnd_Version_Initial = 1;

		// Write the initial version to the custom ini file
		GConfig->SetInt(*SectionName, *KeyName, Cnd_Version_Initial, *Cnd_US_FilePath);

		// Flush changes to disk (this creates the file)
		GConfig->Flush(false, *Cnd_US_FilePath);

		// UE_LOG(LogTemp, Log, TEXT("Custom ini file '%s' created with initial version %d."), *Cnd_US_FilePath, Cnd_Version_Initial);

	}
}

I haven’t used FPlatformFileManager but I guess this would work. However, make sure you also set your variables because I don’t think the engine will read the newly created file on this run. (I think you’ll create it after all the .ini files are loaded)

Redigging this, because I need more help with my custom GUS.

Upon launching the game, my custom game instance (CndGameInstance) calls the CndSysSave_GameUserSettings function BPF_OnGameLaunched() which does this:

void UCndSysSave_UserSettings::BPF_GUS_OnGameLaunched()
{

	bool GUS_Exists = BPF_GUS_DoesExist();

	if (!GUS_Exists)
	{

		UE_LOG(LogTemp, Log, TEXT("CND_DEBUG: GUS - Exists? = NO. Creating .ini file in: %s"), *GUS_FilePath);
		
		BPF_GUS_SetupIniFile();

	}
	else
	{

		UE_LOG(LogTemp, Log, TEXT("CND_DEBUG: GUS - Exists? = YES. Loading settings."));

	}

}

bool UCndSysSave_UserSettings::BPF_GUS_DoesExist()
{

	// Set a custom name for the user settings ini file

	GUS_FilePath = FPaths::ProjectConfigDir() + GUS_CndIniName;
	GGameUserSettingsIni = GUS_FilePath;

	//FString IniFileLocation = FPaths::GeneratedConfigDir() + UGameplayStatics::GetPlatformName() + "/" + GGameUserSettingsIni + ".ini";

	bool FileExists = FPlatformFileManager::Get().GetPlatformFile().FileExists(*GGameUserSettingsIni);

	return FileExists;

}

void UCndSysSave_UserSettings::BPF_GUS_SetupIniFile()
{

	BPF_GUS_RunBenchmark();

	Cnd_Version = 1;

	// Write the initial version to the custom ini file
	GConfig->SetInt(*GUS_Section_CndGame, *GUS_Key_Version, Cnd_Version, GGameUserSettingsIni);

	BPF_GUS_Version_Update();

}

void UCndSysSave_UserSettings::BPF_GUS_Version_Update()
{

	// Validate user settings to the current version.
	Cnd_Version = UE_CND_GAMEUSERSETTINGS_VERSION;

	// Write updated version to the custom ini file
	GConfig->SetInt(*GUS_Section_CndGame, *GUS_Key_Version, Cnd_Version, GGameUserSettingsIni);
	GConfig->SetString(*GUS_Section_CndGame, *GUS_SampleText_Key, *GUS_SampleText_Value, GGameUserSettingsIni);

	// Write to disk
	GConfig->Flush(true, GGameUserSettingsIni);

}

Thing is, the file is not created right away, but only when the editor/game exits. Is there any way I can force the Unreal to create it without exiting, or is it normal?

My Custom Benchmark setup:

void UCndSysSave_UserSettings::BPF_GUS_RunBenchmark()
{

	// Run Benchmark

	GUS_Default = GEngine->GetGameUserSettings();
	GUS_Default->RunHardwareBenchmark(); // This populates the scores

	// Delay score retrieval to allow benchmark to complete
	FTimerHandle TimerHandle;
	FTimerDelegate TimerDel;
	TimerDel.BindUObject(this, &UCndSysSave_UserSettings::BPF_GUS_AutoDetect, true);

	GetWorld()->GetTimerManager().SetTimer(TimerHandle, TimerDel, 1.0f, false);


}

void UCndSysSave_UserSettings::BPF_GUS_AutoDetect(bool BenchmarkRunned)
{

	if (BenchmarkRunned)
	{
		
		

		FString CPUName = FPlatformMisc::GetCPUBrand();
		float CPUScore = GUS_Default->GetLastCPUBenchmarkResult();

		FString GPUName_Default = FPlatformMisc::GetPrimaryGPUBrand();
		FString GPUName_Actual = GRHIAdapterName;
		float GPUScore = GUS_Default->GetLastGPUBenchmarkResult();

		FString RHIName = GDynamicRHI->GetName();

		FString DebugMessage = FString::Printf(TEXT(
			"[CND_DEBUG: GUS - Benchmark Results]\n"			
			"CND_DEBUG: GUS - Benchmark - CPU NAME: %s\n"
			"CND_DEBUG: GUS - Benchmark - CPU SCORE = %f\n"
			"CND_DEBUG: GUS - Benchmark - GPU NAME (DEFAULT): %s\n"
			"CND_DEBUG: GUS - Benchmark - GPU NAME (ACTUAL): %s\n"
			"CND_DEBUG: GUS - Benchmark - GPU SCORE = %f\n"
			"CND_DEBUG: GUS - Benchmark - RHI: %s\n"
			

		),

			*CPUName,
			CPUScore,
			*GPUName_Default,
			* GPUName_Actual,
			GPUScore,
			*RHIName

		);


		UE_LOG(LogTemp, Log, TEXT("%s"), *DebugMessage);

		if (CndFunc::Conditions::IsValue_RangedBetween(GPUScore, 0.0f, 50.0f))
		{
			// Low settings
		}
		else if (CndFunc::Conditions::IsValue_RangedBetween(GPUScore, 50.0f, 100.0f))
		{
			// Medium settings
		}
		else if (CndFunc::Conditions::IsValue_RangedBetween(GPUScore, 100.0f, 150.0f))
		{
			// High settings
		}
		else if (GPUScore > 150.0f)
		{
			// Ultra settings
		}
	}

}