public AlternateFile(string alternateFileText, Mod modForValidating) { var properties = StringStructParser.GetCommaSplitValues(alternateFileText); if (properties.TryGetValue("FriendlyName", out string friendlyName)) { FriendlyName = friendlyName; } if (modForValidating.ModDescTargetVersion >= 6 && string.IsNullOrWhiteSpace(FriendlyName)) { //Cannot be null. Log.Error($"Alternate File does not specify FriendlyName. Mods targeting moddesc >= 6.0 cannot have empty FriendlyName"); ValidAlternate = false; LoadFailedReason = $"At least one specified Alternate File does not specify a FriendlyName, which is required for mods targeting cmmver >= 6.0."; return; } if (!Enum.TryParse(properties["Condition"], out Condition)) { Log.Error("Alternate File specifies unknown/unsupported condition: " + properties["Condition"]); ValidAlternate = false; LoadFailedReason = "Alternate File specifies unknown/unsupported condition: " + properties["Condition"]; return; } if (properties.TryGetValue("ConditionalDLC", out string conditionalDlc)) { var conditionalList = StringStructParser.GetSemicolonSplitList(conditionalDlc); foreach (var dlc in conditionalList) { //if (modForValidating.Game == Mod.MEGame.ME3) //{ if (Enum.TryParse(dlc, out ModJob.JobHeader header) && ModJob.GetHeadersToDLCNamesMap(modForValidating.Game).TryGetValue(header, out var foldername)) { ConditionalDLC.Add(foldername); continue; } //} if (!dlc.StartsWith("DLC_")) { Log.Error("An item in Alternate Files's ConditionalDLC doesn't start with DLC_"); LoadFailedReason = $"Alternate File ({FriendlyName}) specifies conditional DLC but no values match the allowed headers or start with DLC_."; return; } else { ConditionalDLC.Add(dlc); } } } if (!Enum.TryParse(properties["ModOperation"], out Operation)) { Log.Error("Alternate File specifies unknown/unsupported operation: " + properties["ModOperation"]); ValidAlternate = false; LoadFailedReason = "Alternate File specifies unknown/unsupported operation: " + properties["ModOperation"]; return; } if (properties.TryGetValue("Description", out string description)) { Description = description; } if (modForValidating.ModDescTargetVersion >= 6 && string.IsNullOrWhiteSpace(Description)) { //Cannot be null. Log.Error($"Alternate File {FriendlyName} with mod targeting moddesc >= 6.0 cannot have empty Description or missing description"); ValidAlternate = false; LoadFailedReason = $"Alternate File {FriendlyName} does not specify a Description, which is required for mods targeting cmmver >= 6.0."; return; } if (properties.TryGetValue("ModFile", out string modfile)) { ModFile = modfile.TrimStart('\\', '/'); } else { Log.Error("Alternate file in-mod target (ModFile) required but not specified. This value is required for all Alternate files"); ValidAlternate = false; LoadFailedReason = $"Alternate file {FriendlyName} does not declare ModFile but it is required for all Alternate Files."; return; } if (properties.TryGetValue("MultiMappingFile", out string multifilemapping)) { MultiMappingFile = multifilemapping.TrimStart('\\', '/'); } if (properties.TryGetValue("AltFile", out string altfile)) { AltFile = altfile; } else if (AltFile == null && properties.TryGetValue("ModAltFile", out string maltfile)) { AltFile = maltfile; } properties.TryGetValue("SubstituteFile", out SubstituteFile); //Only used in 4.5. In 5.0 and above this became AltFile. //workaround for 4.5 if (modForValidating.ModDescTargetVersion == 4.5 && Operation == AltFileOperation.OP_SUBSTITUTE && SubstituteFile != null) { AltFile = SubstituteFile; } if (!string.IsNullOrEmpty(AltFile)) { AltFile = AltFile.Replace('/', '\\'); //Standardize paths } //This needs reworked from java's hack implementation //Need to identify mods using substitution features if (Operation == AltFileOperation.OP_INSTALL || Operation == AltFileOperation.OP_SUBSTITUTE) { if (MultiMappingFile == null) { //Validate file var altPath = FilesystemInterposer.PathCombine(modForValidating.IsInArchive, modForValidating.ModPath, AltFile); var altFileSourceExists = FilesystemInterposer.FileExists(altPath, modForValidating.Archive); if (!altFileSourceExists) { Log.Error("Alternate file source (AltFile) does not exist: " + AltFile); ValidAlternate = false; LoadFailedReason = $"Alternate file is specified with operation {Operation}, but required file doesn't exist: {AltFile}"; return; } //Ensure it is not part of DLC directory itself. var modFile = FilesystemInterposer.PathCombine(modForValidating.IsInArchive, modForValidating.ModPath, ModFile); //Todo } else { //Multimapping, Todo } } if (properties.TryGetValue("ApplicableAutoText", out string applicableText)) { ApplicableAutoText = applicableText; } else { ApplicableAutoText = "Auto Applied"; } if (properties.TryGetValue("NotApplicableAutoText", out string notApplicableText)) { NotApplicableAutoText = notApplicableText; } else { NotApplicableAutoText = "Not applicable"; } if (Condition == AltFileCondition.COND_MANUAL && properties.TryGetValue("CheckedByDefault", out string checkedByDefault) && bool.TryParse(checkedByDefault, out bool cbd)) { CheckedByDefault = cbd; } CLog.Information($"Alternate file loaded and validated: {FriendlyName}", Settings.LogModStartup); ValidAlternate = true; }
public AlternateFile(string alternateFileText, Mod modForValidating) { var properties = StringStructParser.GetCommaSplitValues(alternateFileText); if (properties.TryGetValue(@"FriendlyName", out string friendlyName)) { FriendlyName = friendlyName; } if (modForValidating.ModDescTargetVersion >= 6 && string.IsNullOrWhiteSpace(FriendlyName)) { //Cannot be null. Log.Error(@"Alternate File does not specify FriendlyName. Mods targeting moddesc >= 6.0 cannot have empty FriendlyName"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_validation_altfile_oneAltDlcMissingFriendlyNameCmm6); return; } if (!Enum.TryParse(properties[@"Condition"], out Condition)) { Log.Error($@"Alternate File specifies unknown/unsupported condition: {properties[@"Condition"]}"); //do not localize ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_validation_altfile_unknownCondition) + properties[@"Condition"]; return; } if (properties.TryGetValue(@"ConditionalDLC", out string conditionalDlc)) { var conditionalList = StringStructParser.GetSemicolonSplitList(conditionalDlc); foreach (var dlc in conditionalList) { //if (modForValidating.Game == Mod.MEGame.ME3) //{ if (Enum.TryParse(dlc, out ModJob.JobHeader header) && ModJob.GetHeadersToDLCNamesMap(modForValidating.Game).TryGetValue(header, out var foldername)) { ConditionalDLC.Add(foldername); continue; } //} if (!dlc.StartsWith(@"DLC_")) { Log.Error(@"An item in Alternate Files's ConditionalDLC doesn't start with DLC_"); LoadFailedReason = M3L.GetString(M3L.string_validation_altfile_conditionalDLCInvalidValue, FriendlyName); return; } else { ConditionalDLC.Add(dlc); } } } if (!Enum.TryParse(properties[@"ModOperation"], out Operation)) { Log.Error(@"Alternate File specifies unknown/unsupported operation: " + properties[@"ModOperation"]); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_validation_altfile_unknownOperation) + properties[@"ModOperation"]; return; } if (properties.TryGetValue(@"Description", out string description)) { Description = description; } if (modForValidating.ModDescTargetVersion >= 6 && string.IsNullOrWhiteSpace(Description)) { //Cannot be null. Log.Error($@"Alternate File {FriendlyName} with mod targeting moddesc >= 6.0 cannot have empty Description or missing description"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altfile_cmmver6RequiresDescription, FriendlyName); return; } if (Operation != AltFileOperation.OP_NOTHING) { if (properties.TryGetValue(@"ModFile", out string modfile)) { ModFile = modfile.TrimStart('\\', '/'); } else { Log.Error($@"Alternate file in-mod target (ModFile) required but not specified. This value is required for all Alternate files. Friendlyname: {FriendlyName}"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altfile_noModFileDeclared, FriendlyName); return; } //todo: implement multimap if (properties.TryGetValue(@"MultiMappingFile", out string multifilemapping)) { MultiMappingFile = multifilemapping.TrimStart('\\', '/'); } if (properties.TryGetValue(@"AltFile", out string altfile)) { AltFile = altfile; } else if (AltFile == null && properties.TryGetValue(@"ModAltFile", out string maltfile)) { AltFile = maltfile; } properties.TryGetValue(@"SubstituteFile", out SubstituteFile); //Only used in 4.5. In 5.0 and above this became AltFile. //workaround for 4.5 if (modForValidating.ModDescTargetVersion == 4.5 && Operation == AltFileOperation.OP_SUBSTITUTE && SubstituteFile != null) { AltFile = SubstituteFile; } if (!string.IsNullOrEmpty(AltFile)) { AltFile = AltFile.Replace('/', '\\'); //Standardize paths } //This needs reworked from java's hack implementation //Need to identify mods using substitution features if (Operation == AltFileOperation.OP_INSTALL || Operation == AltFileOperation.OP_SUBSTITUTE) { if (MultiMappingFile == null) { //Validate file var altPath = FilesystemInterposer.PathCombine(modForValidating.IsInArchive, modForValidating.ModPath, AltFile); var altFileSourceExists = FilesystemInterposer.FileExists(altPath, modForValidating.Archive); if (!altFileSourceExists) { Log.Error(@"Alternate file source (AltFile) does not exist: " + AltFile); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altfile_specifiedAltFileDoesntExist, Operation.ToString(), AltFile); return; } //Ensure it is not part of DLC directory itself. var modFile = FilesystemInterposer.PathCombine(modForValidating.IsInArchive, modForValidating.ModPath, ModFile); //Todo } else { //Multimapping, Todo } } } ApplicableAutoText = properties.TryGetValue(@"ApplicableAutoText", out string applicableText) ? applicableText : M3L.GetString(M3L.string_autoApplied); NotApplicableAutoText = properties.TryGetValue(@"NotApplicableAutoText", out string notApplicableText) ? notApplicableText : M3L.GetString(M3L.string_notApplicable); if (modForValidating.ModDescTargetVersion >= 6.0) { GroupName = properties.TryGetValue(@"OptionGroup", out string groupName) ? groupName : null; } if (Condition == AltFileCondition.COND_MANUAL && properties.TryGetValue(@"CheckedByDefault", out string checkedByDefault) && bool.TryParse(checkedByDefault, out bool cbd)) { CheckedByDefault = cbd; } CLog.Information($@"Alternate file loaded and validated: {FriendlyName}", Settings.LogModStartup); ValidAlternate = true; }
public AlternateDLC(string alternateDLCText, Mod modForValidating, ModJob job) { var properties = StringStructParser.GetCommaSplitValues(alternateDLCText); buildParameterMap(properties); //todo: if statements to check these. if (properties.TryGetValue(@"FriendlyName", out string friendlyName)) { FriendlyName = friendlyName; } if (modForValidating.ModDescTargetVersion >= 6 && string.IsNullOrWhiteSpace(FriendlyName)) { //Cannot be null. Log.Error(@"Alternate DLC does not specify FriendlyName. Mods targeting moddesc >= 6.0 require FriendlyName"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_validation_altdlc_oneAltDlcMissingFriendlyNameCmm6); return; } if (!Enum.TryParse(properties[@"Condition"], out Condition)) { Log.Error($@"Alternate DLC specifies unknown/unsupported condition: {properties[@"Condition"]}"); //do not localize ValidAlternate = false; var condition = properties[@"Condition"]; LoadFailedReason = $@"{M3L.GetString(M3L.string_validation_altdlc_unknownCondition)} {condition}"; return; } if (!Enum.TryParse(properties[@"ModOperation"], out Operation)) { Log.Error($@"Alternate DLC specifies unknown/unsupported operation: {properties[@"ModOperation"]}"); //do not localize ValidAlternate = false; var operation = properties[@"ModOperation"]; LoadFailedReason = $@"{M3L.GetString(M3L.string_validation_altdlc_unknownOperation)} {operation}"; return; } if (properties.TryGetValue(@"Description", out string description)) { Description = description; } if (modForValidating.ModDescTargetVersion >= 6 && string.IsNullOrWhiteSpace(Description)) { //Cannot be null. Log.Error($@"Alternate DLC {FriendlyName} cannot have empty Description or missing Description descriptor as it targets cmmver >= 6"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_cmmver6RequiresDescription, FriendlyName); return; } //OP_NOTHING can have conditions if (properties.TryGetValue(@"ConditionalDLC", out string conditionalDlc)) { var conditionalList = StringStructParser.GetSemicolonSplitList(conditionalDlc); foreach (var dlc in conditionalList) { if (Condition == AltDLCCondition.COND_SPECIFIC_DLC_SETUP) { //check +/- if (!dlc.StartsWith(@"-") && !dlc.StartsWith(@"+")) { Log.Error($@"An item in Alternate DLC's ({FriendlyName}) ConditionalDLC doesn't start with + or -. When using the condition {Condition}, you must precede DLC names with + or -. Bad value: {dlc}"); LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_specificDlcSetupMissingPlusMinus, FriendlyName, Condition, dlc); return; } var prefix = dlc.Substring(0, 1); var realname = dlc.Substring(1); //official headers if (Enum.TryParse(realname, out ModJob.JobHeader header) && ModJob.GetHeadersToDLCNamesMap(modForValidating.Game).TryGetValue(header, out var foldername)) { ConditionalDLC.Add(prefix + foldername); continue; } //dlc mods if (!realname.StartsWith(@"DLC_")) { Log.Error($@"An item in Alternate DLC's ({FriendlyName}) ConditionalDLC doesn't start with DLC_ or is not official header (after the +/- required by {Condition}). Bad value: {dlc}"); LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_specificDlcSetupInvalidDlcName, FriendlyName, Condition, dlc); return; } else { ConditionalDLC.Add(prefix + realname); } } else { if (Enum.TryParse(dlc, out ModJob.JobHeader header) && ModJob.GetHeadersToDLCNamesMap(modForValidating.Game).TryGetValue(header, out var foldername)) { ConditionalDLC.Add(foldername); continue; } if (!dlc.StartsWith(@"DLC_")) { Log.Error($@"An item in Alternate DLC's ({FriendlyName}) ConditionalDLC doesn't start with DLC_ or is not official header"); LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_conditionalDLCInvalidValue, FriendlyName); return; } else { ConditionalDLC.Add(dlc); } } } } if (Operation != AltDLCOperation.OP_NOTHING) { int multilistid = -1; if (Operation == AltDLCOperation.OP_ADD_MULTILISTFILES_TO_CUSTOMDLC) { if (properties.TryGetValue(@"MultiListRootPath", out var rootpath)) { MultiListRootPath = rootpath.TrimStart('\\', '/').Replace('/', '\\'); } else { Log.Error($@"Alternate DLC ({FriendlyName}) specifies operation OP_ADD_MULTILISTFILES_TO_CUSTOMDLC but does not specify the required item MultiListRootPath."); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_altdlc_multilistMissingMultiListRootPath, FriendlyName); return; } if (properties.TryGetValue(@"MultiListId", out string multilistidstr) && int.TryParse(multilistidstr, out multilistid)) { if (job.MultiLists.TryGetValue(multilistid, out var ml)) { MultiListSourceFiles = ml; } else { Log.Error($@"Alternate DLC ({FriendlyName}) Multilist ID does not exist as part of the task: multilist" + multilistid); ValidAlternate = false; var id = @"multilist" + multilistid; LoadFailedReason = M3L.GetString(M3L.string_interp_altdlc_multilistMissingMultiListX, FriendlyName, id); return; } } else { Log.Error($@"Alternate DLC ({FriendlyName}) specifies operation OP_ADD_MULTILISTFILES_TO_CUSTOMDLC but does not specify the MultiListId attribute, or it could not be parsed to an integer."); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_altdlc_multilistIdNotIntegerOrMissing, FriendlyName); return; } } else { if (properties.TryGetValue(@"ModAltDLC", out string altDLCFolder)) { AlternateDLCFolder = altDLCFolder.Replace('/', '\\'); } else { Log.Error(@"Alternate DLC does not specify ModAltDLC but is required"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_missingModAltDLC, FriendlyName); return; } } if (properties.TryGetValue(@"ModDestDLC", out string destDLCFolder)) { DestinationDLCFolder = destDLCFolder.Replace('/', '\\'); } else { Log.Error(@"Alternate DLC does not specify ModDestDLC but is required"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_missingModDestDLC, FriendlyName); return; } //todo: Validate target in mod folder //Validation if (string.IsNullOrWhiteSpace(AlternateDLCFolder) && MultiListRootPath == null) { Log.Error($@"Alternate DLC directory (ModAltDLC) not specified for { FriendlyName}"); LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_sourceDirectoryNotSpecifiedForModAltDLC, FriendlyName); return; } if (string.IsNullOrWhiteSpace(DestinationDLCFolder)) { Log.Error($@"Destination DLC directory (ModDestDLC) not specified for {FriendlyName}"); LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_destinationDirectoryNotSpecifiedForModDestDLC, FriendlyName); return; } if (AlternateDLCFolder != null) { AlternateDLCFolder = AlternateDLCFolder.TrimStart('\\', '/').Replace('/', '\\'); //Check ModAltDLC directory exists var localAltDlcDir = FilesystemInterposer.PathCombine(modForValidating.IsInArchive, modForValidating.ModPath, AlternateDLCFolder); if (!FilesystemInterposer.DirectoryExists(localAltDlcDir, modForValidating.Archive)) { Log.Error($@"Alternate DLC directory (ModAltDLC) does not exist: {AlternateDLCFolder}"); LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_sourceDirectoryDoesntExist, FriendlyName, AlternateDLCFolder); return; } } else if (MultiListRootPath != null) { foreach (var multif in MultiListSourceFiles) { var path = FilesystemInterposer.PathCombine(modForValidating.IsInArchive, modForValidating.ModPath, MultiListRootPath, multif); if (!FilesystemInterposer.FileExists(path, modForValidating.Archive)) { Log.Error($@"Alternate DLC ({FriendlyName}) specifies a multilist (index {multilistid}) that contains file that does not exist: {multif}"); LoadFailedReason = M3L.GetString(M3L.string_interp_altdlc_multilistMissingFileInMultilist, FriendlyName, multilistid, multif); return; } } } } var dlcReqs = properties.TryGetValue(@"DLCRequirements", out string _dlcReqs) ? _dlcReqs.Split(';') : null; if (dlcReqs != null) { var reqList = new List <string>(); foreach (var req in dlcReqs) { //official headers if (Enum.TryParse(req, out ModJob.JobHeader header) && ModJob.GetHeadersToDLCNamesMap(modForValidating.Game).TryGetValue(header, out var foldername)) { reqList.Add(foldername); continue; } //dlc mods if (!req.StartsWith(@"DLC_")) { Log.Error($@"An item in Alternate DLC's ({FriendlyName}) DLCRequirements doesn't start with DLC_ or is not official header. Bad value: {req}"); LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_dlcRequirementInvalid, FriendlyName, req); return; } else { reqList.Add(req); } } DLCRequirementsForManual = reqList.ToArray(); } if (Condition == AltDLCCondition.COND_SPECIFIC_SIZED_FILES) { var requiredFilePaths = properties.TryGetValue(@"RequiredFileRelativePaths", out string _requiredFilePaths) ? _requiredFilePaths.Split(';').ToList() : new List <string>(); var requiredFileSizes = properties.TryGetValue(@"RequiredFileSizes", out string _requiredFileSizes) ? _requiredFileSizes.Split(';').ToList() : new List <string>(); if (requiredFilePaths.Count() != requiredFileSizes.Count()) { Log.Error($@"Alternate DLC {FriendlyName} uses COND_SPECIFIC_SIZED_FILES but the amount of items in the RequiredFileRelativePaths and RequiredFileSizes lists are not equal"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_specificSizedFilesMismatchedParams, FriendlyName); return; } for (int i = 0; i < requiredFilePaths.Count(); i++) { var reqFile = requiredFilePaths[i]; var reqSizeStr = requiredFileSizes[i]; if (reqFile.Contains(@"..")) { Log.Error($@"Alternate DLC {FriendlyName} RequiredFileRelativePaths item {reqFile} is invalid: Values cannot contain '..' for security reasons"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_specificSizedFilesContainsIllegalPattern, FriendlyName, reqFile); return; } reqFile = reqFile.Replace('/', '\\').TrimStart('\\'); //standardize if (long.TryParse(reqSizeStr, out var reqSize) && reqSize >= 0) { RequiredSpecificFiles[reqFile] = reqSize; } else { Log.Error($@"Alternate DLC {FriendlyName} RequiredFileSizes item {reqFile} is invalid: {reqSizeStr}. Values must be greater than or equal to zero."); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_specificSizedFileMustBeLargerThanZero, FriendlyName, reqFile, reqSizeStr); return; } } if (!RequiredSpecificFiles.Any()) { Log.Error($@"Alternate DLC {FriendlyName} is invalid: COND_SPECIFIC_SIZED_FILES is specified as the condition but there are no values in RequiredFileRelativePaths/RequiredFileSizes"); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_validation_altdlc_specificSizedFilesMissingRequiredParams, FriendlyName); return; } } ApplicableAutoText = properties.TryGetValue(@"ApplicableAutoText", out string applicableText) ? applicableText : M3L.GetString(M3L.string_autoApplied); NotApplicableAutoText = properties.TryGetValue(@"NotApplicableAutoText", out string notApplicableText) ? notApplicableText : M3L.GetString(M3L.string_notApplicable); if (modForValidating.ModDescTargetVersion >= 6.0) { GroupName = properties.TryGetValue(@"OptionGroup", out string groupName) ? groupName : null; //TODO: FORCE OPTIONGROUP TO HAVE ONE ITEM CHECKEDBYDFEAULT. HAVE TO CHECK AT HIGHER LEVEL IN PARSER } if (Condition == AltDLCCondition.COND_MANUAL && properties.TryGetValue(@"CheckedByDefault", out string checkedByDefault) && bool.TryParse(checkedByDefault, out bool cbd)) { CheckedByDefault = cbd; } if (Condition != AltDLCCondition.COND_MANUAL && Condition != AltDLCCondition.COND_SPECIFIC_SIZED_FILES && Condition != AltDLCCondition.INVALID_CONDITION) { //ensure conditional dlc list has at least one item. if (ConditionalDLC.Count == 0) { Log.Error($@"Alternate DLC {FriendlyName} cannot have empty or missing Conditional DLC list, as it does not use COND_MANUAL or COND_SPECIFIC_SIZED_FILES."); ValidAlternate = false; LoadFailedReason = M3L.GetString(M3L.string_interp_altdlc_emptyConditionalDLCList, FriendlyName); return; } } CLog.Information($@"AlternateDLC loaded and validated: {FriendlyName}", Settings.LogModStartup); ValidAlternate = true; }