// Copyright Epic Games, Inc. All Rights Reserved. #include "CustomConfigFile.h" #include "Misc/FileHelper.h" #include "Utility.h" namespace UGSCore { static const TCHAR* ConfigSeparatorCharacters = TEXT("(),= \t\""); //// FCustomConfigObject //// FCustomConfigObject::FCustomConfigObject() { } FCustomConfigObject::FCustomConfigObject(const TCHAR* Text) { ParseConfigString(Text); } FCustomConfigObject::FCustomConfigObject(const FCustomConfigObject& BaseObject, const TCHAR* Text) : Pairs(BaseObject.Pairs) { ParseConfigString(Text); } void FCustomConfigObject::ParseConfigString(const TCHAR* Text) { int Idx = 0; if(ParseConfigToken(Text, Idx) == "(") { while(Text[Idx] != 0) { // Read the next key/value pair FString Key = ParseConfigToken(Text, Idx); if(ParseConfigToken(Text, Idx) == "=") { FString Value = ParseConfigToken(Text, Idx); SetValue(*Key, *Value); } // Check for the end of the list, or a comma before the next pair for(;;) { FString Token = ParseConfigToken(Text, Idx); if(Token == ",") { break; } if(Token == ")" || Token.Len() == 0) { return; } } } } } FString FCustomConfigObject::ParseConfigToken(const TCHAR* Text, int& Idx) { // Skip whitespace while(Text[Idx] != 0 && FChar::IsWhitespace(Text[Idx])) { Idx++; } if(Text[Idx] == 0) { return FString(); } // Read the token if(Text[Idx] == '\"') { FString Token; while(Text[++Idx] != 0) { if(Text[Idx] == '\"') { Idx++; break; } if(Text[Idx] == '\\' && Text[Idx + 1] != 0) { Idx++; } Token += Text[Idx]; } return Token; } else if(FCString::Strchr(ConfigSeparatorCharacters, Text[Idx]) != nullptr) { FString Token; Token.AppendChar(Text[Idx++]); return Token; } else { int StartIdx = Idx; while(Text[Idx] != 0 && FCString::Strchr(ConfigSeparatorCharacters, Text[Idx]) == nullptr) { Idx++; } return FString(Idx - StartIdx, Text + StartIdx); } } bool FCustomConfigObject::HasKey(const TCHAR* Key) const { const TCHAR* CurrentValue; return TryGetValue(Key, CurrentValue); } bool FCustomConfigObject::HasValue(const TCHAR* Key, const TCHAR* Value) const { const TCHAR* CurrentValue; return TryGetValue(Key, CurrentValue) && FCString::Stricmp(CurrentValue, Value) == 0; } bool FCustomConfigObject::TryGetValue(const TCHAR* Key, const TCHAR*& OutValue) const { for(int Idx = 0; Idx < Pairs.Num(); Idx++) { if(Pairs[Idx].Key == Key) { OutValue = *Pairs[Idx].Value; return true; } } return false; } bool FCustomConfigObject::TryGetValue(const TCHAR* Key, FString& OutValue) const { const TCHAR* Value; if(TryGetValue(Key, Value)) { OutValue = Value; return true; } return false; } bool FCustomConfigObject::TryGetValue(const TCHAR* Key, FGuid& OutValue) const { const TCHAR* StringValue; if(TryGetValue(Key, StringValue)) { FGuid GuidValue; if(FGuid::Parse(StringValue, GuidValue)) { OutValue = GuidValue; return true; } } return false; } bool FCustomConfigObject::TryGetValue(const TCHAR* Key, int& OutValue) const { const TCHAR* StringValue; if(TryGetValue(Key, StringValue)) { TCHAR* EndPtr = nullptr; int IntegerValue = FCString::Strtoi(StringValue, &EndPtr, 10); if(*EndPtr == 0) { OutValue = IntegerValue; return true; } } return false; } bool FCustomConfigObject::TryGetValue(const TCHAR* Key, bool& OutValue) const { const TCHAR* StringValue; if(TryGetValue(Key, StringValue)) { if(FCString::Stricmp(StringValue, TEXT("True")) == 0) { OutValue = true; return true; } else if(FCString::Stricmp(StringValue, TEXT("False")) == 0) { OutValue = false; return true; } } return false; } const TCHAR* FCustomConfigObject::GetValueOrDefault(const TCHAR* Key, const TCHAR* DefaultValue) const { const TCHAR* Value; if(TryGetValue(Key, Value)) { return Value; } else { return DefaultValue; } } FGuid FCustomConfigObject::GetValueOrDefault(const TCHAR* Key, const FGuid& DefaultValue) const { FGuid Value; if(TryGetValue(Key, Value)) { return Value; } else { return DefaultValue; } } int FCustomConfigObject::GetValueOrDefault(const TCHAR* Key, int DefaultValue) const { int Value; if(TryGetValue(Key, Value)) { return Value; } else { return DefaultValue; } } bool FCustomConfigObject::GetValueOrDefault(const TCHAR* Key, bool DefaultValue) const { bool Value; if(TryGetValue(Key, Value)) { return Value; } else { return DefaultValue; } } void FCustomConfigObject::SetValue(const TCHAR* Key, const TCHAR* Value) { for(int Idx = 0; Idx < Pairs.Num(); Idx++) { if(Pairs[Idx].Key == Key) { Pairs[Idx] = TTuple(Key, Value); return; } } Pairs.Add(TTuple(Key, Value)); } void FCustomConfigObject::SetValue(const TCHAR* Key, const FGuid& Value) { SetValue(Key, *Value.ToString()); } void FCustomConfigObject::SetValue(const TCHAR* Key, int Value) { SetValue(Key, *FString::Printf(TEXT("%d"), Value)); } void FCustomConfigObject::SetValue(const TCHAR* Key, bool Value) { SetValue(Key, Value? TEXT("True") : TEXT("False")); } void FCustomConfigObject::SetDefaults(const FCustomConfigObject& Other) { for(const TTuple& Pair : Other.Pairs) { if(!HasKey(*Pair.Key)) { SetValue(*Pair.Key, *Pair.Value); } } } void FCustomConfigObject::AddOverrides(const FCustomConfigObject& Object, const FCustomConfigObject* DefaultObject) { for(const TTuple& Pair : Object.Pairs) { if(DefaultObject == nullptr || !DefaultObject->HasValue(*Pair.Key, *Pair.Value)) { SetValue(*Pair.Key, *Pair.Value); } } } FString FCustomConfigObject::ToString(const FCustomConfigObject* BaseObject) const { FString Result; Result.Append("("); for(const TTuple& Pair : Pairs) { if(BaseObject == nullptr || !BaseObject->HasValue(*Pair.Key, *Pair.Value)) { if(Result.Len() > 1) { Result.Append(", "); } Result.Append(Pair.Key); Result.Append("="); if(Pair.Value.Len() == 0) { Result.Append("\"\""); } else { Result.Append(FString(TEXT("\"")) + Pair.Value.Replace(TEXT("\\"), TEXT("\\\\")).Replace(TEXT("\""), TEXT("\\\"")) + FString(TEXT("\""))); } } } Result.Append(")"); return Result; } FString FCustomConfigObject::ToString() const { return ToString(nullptr); } //// FCustomConfigSection //// FCustomConfigSection::FCustomConfigSection(FString&& InName) : Name(InName) { } void FCustomConfigSection::Clear() { Pairs.Empty(); } void FCustomConfigSection::SetValue(const TCHAR* Key, int Value) { Pairs.FindOrAdd(Key) = FString::Printf(TEXT("%d"), Value); } void FCustomConfigSection::SetValue(const TCHAR* Key, bool Value) { Pairs.FindOrAdd(Key) = Value? "1" : "0"; } void FCustomConfigSection::SetValue(const TCHAR* Key, const TCHAR* Value) { if(Value == nullptr) { RemoveValue(Key); } else { Pairs.FindOrAdd(Key) = Value; } } void FCustomConfigSection::SetValues(const TCHAR* Key, const TArray& Values) { if(Values.Num() == 0) { RemoveValue(Key); } else { Pairs.FindOrAdd(Key) = FString::Join(Values, TEXT("\n")); } } void FCustomConfigSection::SetValues(const TCHAR* Key, const TArray& Values) { if(Values.Num() == 0) { RemoveValue(Key); } else { FString Value = Values[0].ToString(); for(int Idx = 1; Idx < Values.Num(); Idx++) { Value += "\n"; Value += Values[Idx].ToString(); } Pairs.FindOrAdd(Key) = Value; } } void FCustomConfigSection::SetValues(const TCHAR* Key, const TArray& Values) { if(Values.Num() == 0) { RemoveValue(Key); } else { FString Value = FString::Printf(TEXT("%d"), Values[0]); for(int Idx = 1; Idx < Values.Num(); Idx++) { Value += TEXT("\n"); Value += FString::Printf(TEXT("%d"), Values[Idx]); } Pairs.FindOrAdd(Key) = Value; } } void FCustomConfigSection::AppendValue(const TCHAR* Key, const TCHAR* Value) { FString* CurrentValue = Pairs.Find(Key); if(CurrentValue != nullptr) { (*CurrentValue) += FString::Printf(TEXT("\n%s"), Value); } else { Pairs.FindOrAdd(Key) = Value; } } void FCustomConfigSection::RemoveValue(const TCHAR* Key) { Pairs.Remove(Key); } int FCustomConfigSection::GetValue(const TCHAR* Key, int DefaultValue) const { const TCHAR* ValueString = GetValue(Key); if(ValueString != nullptr) { TCHAR* EndPtr = nullptr; int Value = FCString::Strtoi(ValueString, &EndPtr, 10); if(*EndPtr == 0) { return Value; } } return DefaultValue; } bool FCustomConfigSection::GetValue(const TCHAR* Key, bool DefaultValue) const { return GetValue(Key, DefaultValue? 1 : 0) != 0; } const TCHAR* FCustomConfigSection::GetValue(const TCHAR* Key, const TCHAR* DefaultValue) const { const FString* Value = Pairs.Find(Key); if(Value == nullptr) { return DefaultValue; } else { return **Value; } } bool FCustomConfigSection::TryGetValue(const TCHAR* Key, FString& OutValue) const { const TCHAR* ValueString = GetValue(Key); if(ValueString != nullptr) { OutValue = ValueString; return true; } return false; } bool FCustomConfigSection::TryGetValue(const TCHAR* Key, int32& OutValue) const { const TCHAR* ValueString = GetValue(Key); if(ValueString != nullptr) { TCHAR* EndPtr = nullptr; int Value = FCString::Strtoi(ValueString, &EndPtr, 10); if(*EndPtr == 0) { OutValue = Value; return true; } } return false; } bool FCustomConfigSection::TryGetValues(const TCHAR* Key, TArray& Values) const { const FString* Value = Pairs.Find(Key); if(Value != nullptr) { Value->ParseIntoArray(Values, TEXT("\n"), false); return true; } return false; } bool FCustomConfigSection::TryGetValues(const TCHAR* Key, TArray& Values) const { TArray StringValues; if(TryGetValues(Key, StringValues)) { for(const FString& StringValue : StringValues) { FGuid Guid; if(FGuid::Parse(StringValue, Guid)) { Values.Add(Guid); } } return true; } return false; } bool FCustomConfigSection::TryGetValues(const TCHAR* Key, TArray& Values) const { TArray StringValues; if(TryGetValues(Key, StringValues)) { for(const FString& StringValue : StringValues) { int IntegerValue; if(FUtility::TryParse(*StringValue, IntegerValue)) { Values.Add(IntegerValue); } } return true; } return false; } //// FCustomConfigFile //// FCustomConfigFile::FCustomConfigFile() { } void FCustomConfigFile::Load(const TCHAR* FileName) { TArray Lines; FFileHelper::LoadFileToStringArray(Lines, FileName); Parse(Lines); } void FCustomConfigFile::Parse(const TArray& Lines) { TSharedPtr CurrentSection; for(const FString& Line : Lines) { FString TrimLine = Line.TrimStartAndEnd(); if(!TrimLine.StartsWith(";")) { if(TrimLine.StartsWith("[") && TrimLine.EndsWith("]")) { FString SectionName = TrimLine.Mid(1, TrimLine.Len() - 2).TrimStartAndEnd(); CurrentSection = FindOrAddSection(*SectionName); } else if(CurrentSection.IsValid()) { int EqualsIdx; if(TrimLine.FindChar(TEXT('='), EqualsIdx)) { FString Value = Line.Mid(EqualsIdx + 1).TrimStart(); if(TrimLine.StartsWith("+")) { FString Key = TrimLine.Mid(1, EqualsIdx - 1).TrimStartAndEnd(); CurrentSection->AppendValue(*Key, *Value); } else { FString Key = TrimLine.Mid(0, EqualsIdx).TrimEnd(); CurrentSection->SetValue(*Key, *Value); } } } } } } void FCustomConfigFile::Save(const TCHAR* FileName) { TArray Lines; for(int Idx = 0; Idx < Sections.Num(); Idx++) { Lines.Add(FString::Printf(TEXT("[%s]"), *Sections[Idx]->Name)); for(const TTuple& Pair : Sections[Idx]->Pairs) { if(Pair.Value.Contains(TEXT("\n"))) { TArray Values; Pair.Value.ParseIntoArrayLines(Values, false); for(const FString& Value : Values) { Lines.Add(FString::Printf(TEXT("+%s=%s"), *Pair.Key, *Value)); } } else { Lines.Add(FString::Printf(TEXT("%s=%s"), *Pair.Key, *Pair.Value)); } } if(Idx < Sections.Num() - 1) { Lines.Add(FString()); } } FFileHelper::SaveStringArrayToFile(Lines, FileName); } TSharedPtr FCustomConfigFile::FindSection(const TCHAR* Name) { for(const TSharedPtr& Section : Sections) { if(Section->Name == Name) { return Section; } } return nullptr; } TSharedPtr FCustomConfigFile::FindSection(const TCHAR* Name) const { for(const TSharedPtr& Section : Sections) { if(Section->Name == Name) { return Section; } } return nullptr; } TSharedRef FCustomConfigFile::FindOrAddSection(const TCHAR* Name) { TSharedPtr Section = FindSection(Name); if(!Section.IsValid()) { TSharedRef NewSection = MakeShared(Name); Sections.Add(NewSection); return NewSection; } return Section.ToSharedRef(); } void FCustomConfigFile::SetValue(const TCHAR* Key, int Value) { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedRef Section = FindOrAddSection(*FString(Dot - Key, Key)); Section->SetValue(Dot + 1, Value); } void FCustomConfigFile::SetValue(const TCHAR* Key, bool Value) { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedRef Section = FindOrAddSection(*FString(Dot - Key, Key)); Section->SetValue(Dot + 1, Value); } void FCustomConfigFile::SetValue(const TCHAR* Key, const TCHAR* Value) { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedRef Section = FindOrAddSection(*FString(Dot - Key, Key)); Section->SetValue(Dot + 1, Value); } void FCustomConfigFile::SetValues(const TCHAR* Key, const TArray& Values) { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedRef Section = FindOrAddSection(*FString(Dot - Key, Key)); Section->SetValues(Dot + 1, Values); } bool FCustomConfigFile::GetValue(const TCHAR* Key, bool DefaultValue) const { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedPtr Section = FindSection(*FString(Dot - Key, Key)); return Section.IsValid()? Section->GetValue(Dot + 1, DefaultValue) : DefaultValue; } int32 FCustomConfigFile::GetValue(const TCHAR* Key, int32 DefaultValue) const { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedPtr Section = FindSection(*FString(Dot - Key, Key)); return Section.IsValid()? Section->GetValue(Dot + 1, DefaultValue) : DefaultValue; } const TCHAR* FCustomConfigFile::GetValue(const TCHAR* Key, const TCHAR* DefaultValue) const { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedPtr Section = FindSection(*FString(Dot - Key, Key)); return Section.IsValid()? Section->GetValue(Dot + 1, DefaultValue) : DefaultValue; } bool FCustomConfigFile::TryGetValue(const TCHAR* Key, FString& OutValue) const { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedPtr Section = FindSection(*FString(Dot - Key, Key)); return Section.IsValid()? Section->TryGetValue(Key, OutValue) : false; } bool FCustomConfigFile::TryGetValue(const TCHAR* Key, int32& OutValue) const { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedPtr Section = FindSection(*FString(Dot - Key, Key)); return Section.IsValid()? Section->TryGetValue(Key, OutValue) : false; } bool FCustomConfigFile::TryGetValues(const TCHAR* Key, TArray& Values) const { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedPtr Section = FindSection(*FString(Dot - Key, Key)); return Section.IsValid()? Section->TryGetValues(Dot + 1, Values) : false; } bool FCustomConfigFile::TryGetValues(const TCHAR* Key, TArray &Values) const { const TCHAR* Dot = FCString::Strchr(Key, TEXT('.')); check(Dot != nullptr); TSharedPtr Section = FindSection(*FString(Dot - Key, Key)); return Section.IsValid()? Section->TryGetValues(Dot + 1, Values) : false; } } // namespace UGSCore