public override void TrackRemoteScript(TES5ScriptHeader scriptHeader) { this.trackedScript = scriptHeader; ITES5Type ourNativeType = this.propertyType.NativeType; ITES5Type remoteNativeType = this.trackedScript.ScriptType.NativeType; /* * Scenario 1 - types are equal or the remote type is higher than ours in which case we do nothing as they have the good type anyway */ if (ourNativeType == remoteNativeType || TES5InheritanceGraphAnalyzer.IsExtending(remoteNativeType, ourNativeType)) { return; } /* * Scenario 2 - Our current native type is extending remote script"s extended type - we need to set it properly */ else if (TES5InheritanceGraphAnalyzer.IsExtending(ourNativeType, remoteNativeType)) { this.trackedScript.SetNativeType(ourNativeType); } else { throw new ConversionException(nameof(TES5Property) + "." + nameof(TrackRemoteScript) + ": The definitions of local property type and remote property type have diverged. (Ours: " + ourNativeType.Value + ", remote: " + remoteNativeType.Value); } }
public void TrackRemoteScript(TES5ScriptHeader scriptHeader) { this.trackedScript = scriptHeader; ITES5Type ourNativeType = this.propertyType.NativeType; ITES5Type remoteNativeType = this.trackedScript.ScriptType.NativeType; /* * Scenario 1 - types are equal or the remote type is higher than ours in which case we do nothing as they have the good type anyway */ if (TES5InheritanceGraphAnalyzer.IsTypeOrExtendsType(remoteNativeType, ourNativeType)) { return; } /* * Scenario 2 - Our current native type is extending remote script"s extended type - we need to set it properly */ else if (TES5InheritanceGraphAnalyzer.IsExtending(ourNativeType, remoteNativeType)) { this.trackedScript.SetNativeType(ourNativeType); } else { //WTM: Note: Special Cases: Let some script transpile without throwing. if (!( (ourNativeType == TES5BasicType.T_ACTIVATOR && remoteNativeType == TES5BasicType.T_OBJECTREFERENCE && (scriptHeader.EDID == "SE01MetronomeScript" || scriptHeader.EDID == "SE11TrigZoneHowlingHallsEnterSCRIPT")) || (ourNativeType == TES5BasicType.T_ACTORBASE && remoteNativeType == TES5BasicType.T_ACTOR && (scriptHeader.EDID == "HieronymusLexScript" || scriptHeader.EDID == "SERunsInCirclesSCRIPT")))) { throw new ConversionException(nameof(TES5Property) + "." + nameof(TrackRemoteScript) + ": The definitions of local property type and remote property type have diverged. (Ours: " + ourNativeType.Value + ", remote: " + remoteNativeType.Value + ")"); } } }
public TES5Property(string propertyName, ITES5Type propertyType, string referenceEDID) : base(AddPropertyNameSuffix(propertyName)) { this.propertyType = propertyType; //If we"re tracking a script, this won"t be used anymore this.referenceEDID = referenceEDID; this.trackedScript = null; }
public static void GenerateINFOAddTopicScripts(ESMAnalyzer esmAnalyzer, BuildTracker buildTracker, IBuildTarget tifBuildTarget) { TES5TypeInferencer typeInferencer = new TES5TypeInferencer(esmAnalyzer); TES5ObjectCallFactory objectCallFactory = new TES5ObjectCallFactory(typeInferencer); TES4TopicsToTES5GlobalVariableFinder globalVariableFinder = new TES4TopicsToTES5GlobalVariableFinder(); TES5GlobalVariables globalVariables = esmAnalyzer.GlobalVariables; var builtTIFs = buildTracker.GetBuiltScripts(BuildTargetFactory.TIFName); foreach (TES4Record infoRecord in esmAnalyzer.GetRecords().Where(r => r.RecordType == TES4RecordType.INFO)) { TES4SubrecordData[] names = infoRecord.GetSubrecords("NAME").ToArray(); if (names.Any()) { string fragment0Name = TES5FragmentFactory.GetFragmentName(0); string nameTES5FormIDHex = (infoRecord.FormID + 0x01000000).ToString("x8"); string scriptName = TES5ReferenceFactory.tif_Prefix + "_" + nameTES5FormIDHex; TES5Script? infoTIF = builtTIFs.Where(s => s.Key == scriptName).Select(s => s.Value.Script).FirstOrDefault(); TES5FunctionCodeBlock fragment0; if (infoTIF != null) { fragment0 = infoTIF.BlockList.Blocks.OfType <TES5FunctionCodeBlock>().Where(b => b.BlockName == fragment0Name).First(); } else { TES5ScriptHeader scriptHeader = TES5ScriptHeaderFactory.GetFromCacheOrConstructByBasicType(scriptName, TES5BasicType.T_TOPICINFO, TES5TypeFactory.TES4_Prefix, true); TES5GlobalScope globalScope = new TES5GlobalScope(scriptHeader); TES5FunctionScope functionScope = new TES5FunctionScope(fragment0Name); functionScope.AddParameter(new TES5SignatureParameter("akSpeakerRef", TES5BasicType.T_OBJECTREFERENCE, true)); TES5LocalScope localScope = new TES5LocalScope(functionScope); TES5CodeScope codeScope = TES5CodeScopeFactory.CreateCodeScope(localScope); fragment0 = new TES5FunctionCodeBlock(functionScope, codeScope, TES5VoidType.Instance, false, true); TES5BlockList blockList = new TES5BlockList() { fragment0 }; infoTIF = new TES5Script(globalScope, blockList, true); string outputPath = tifBuildTarget.GetTranspileToPath(scriptName); TES5Target target = new TES5Target(infoTIF, outputPath); buildTracker.RegisterBuiltScript(tifBuildTarget, target); } foreach (TES4SubrecordData name in names) { int formID = infoRecord.ExpandBytesIntoFormID(name); TES4Record addedTopic = esmAnalyzer.GetRecordByFormID(formID); Tuple <int, string>?globalVariable = globalVariableFinder.GetGlobalVariableNullable(addedTopic.FormID); string globalVariableEditorID = globalVariable != null ? globalVariable.Item2 : globalVariableFinder.GetGlobalVariableEditorID(addedTopic.GetEditorID()); Nullable <int> globalVariableTES5FormID = globalVariable != null ? globalVariable.Item1 : (Nullable <int>)null; TES5Property topicAddedProperty = TES5PropertyFactory.ConstructWithTES5FormID(globalVariableEditorID, TES5BasicType.T_GLOBALVARIABLE, globalVariableEditorID, globalVariableTES5FormID); infoTIF.GlobalScope.AddPropertyIfNotExists(topicAddedProperty); TES5Reference topicAddedReference = TES5ReferenceFactory.CreateReferenceToVariableOrProperty(topicAddedProperty); fragment0.AddChunk(objectCallFactory.CreateObjectCall(topicAddedReference, "SetValueInt", new TES5ObjectCallArguments() { new TES5Integer(1) })); } } } }
private static string GeneratePropertyName(TES5ScriptHeader header, TES5Property property) { return("col_" + property.GetPropertyNameWithoutSuffix() + "_" + PHPFunction.MD5(header.EscapedScriptName).Substring(0, 4) //WTM: Note: Instead of using an MD5 hash, I tried the below (where index was the property index within the script's TES5GlobalScope. //It worked, but I don't think the names matched up well with what GECK generates, so I've commented it for now. //header.EscapedScriptName + "_" + index ); }
private void InferenceWithCustomType(ITES5VariableOrProperty variable, ITES5Type type, TES5MultipleScriptsScope multipleScriptsScope) { /* * We"re referencing another script - find the script and make it a variable that property will track remotely */ TES5ScriptHeader scriptHeader = multipleScriptsScope.GetScriptHeaderOfScript(type.OriginalName); variable.TrackRemoteScript(scriptHeader); }
private static string GeneratePropertyName(TES5ScriptHeader header, TES5Property property) { if (property.AllowNameTransformation) { return("col_" + property.OriginalName + "_" + PHPFunction.MD5(header.EscapedScriptName).Substring(0, 4)); //WTM: Note: Instead of using an MD5 hash, I tried the below (where index was the property index within the script's TES5GlobalScope). //"col_" + property.OriginalName + "_" + header.EscapedScriptName + "_" + index //It worked, but I don't think the names match with what GECKFrontend generates, so I've commented it for now. } return(property.OriginalName); }
public TES5GlobalScope Build(string scriptPath, TES5GlobalVariables globalVariables) { TES4Script parsedScript = this.standaloneParsingService.ParseOrGetFromCache(scriptPath); TES5ScriptHeader scriptHeader = this.CreateHeader(parsedScript); TES4VariableDeclarationList?variableList = parsedScript.VariableDeclarationList; TES5GlobalScope globalScope = new TES5GlobalScope(scriptHeader); if (variableList != null) { propertyFactory.CreateAndAddProperties(variableList, globalScope, globalVariables); } return(globalScope); }
public TES5GlobalScope Build(string sourcePath, TES5GlobalVariables globalVariables) { string scriptName = Path.GetFileNameWithoutExtension(sourcePath); TES4VariableDeclarationList variableList = fragmentsReferencesBuilder.BuildVariableDeclarationList(sourcePath, scriptName, fragmentType); //Create the header. TES5ScriptHeader scriptHeader = TES5ScriptHeaderFactory.GetFromCacheOrConstructByBasicType(scriptName, scriptType, scriptNamePrefix, true); TES5GlobalScope globalScope = new TES5GlobalScope(scriptHeader); if (variableList != null) { propertyFactory.CreateAndAddProperties(variableList, globalScope, globalVariables); } return(globalScope); }
public TES5GlobalScope Build(string sourcePath, TES5GlobalVariables globalVariables) { string scriptName = Path.GetFileNameWithoutExtension(sourcePath); string referencesPath = Path.Combine(Path.GetDirectoryName(sourcePath), scriptName + ".references"); //Create the header. TES5ScriptHeader scriptHeader = new TES5ScriptHeader(scriptName, TES5BasicType.T_QUEST, "", true); TES5GlobalScope globalScope = new TES5GlobalScope(scriptHeader); TES4VariableDeclarationList variableList = FragmentsReferencesBuilder.buildVariableDeclarationList(referencesPath); if (variableList != null) { TES5PropertiesFactory.CreateProperties(variableList, globalScope, globalVariables); } return(globalScope); }
/* * @todo REFACTOR, it"s really ugly! * @throws ConversionException */ public ITES5Type GetScriptTypeByEDID(string edid) { string edidLower = edid.ToLower(); ITES5Type?value; if (this.edidLowerCache.TryGetValue(edidLower, out value)) { return(value); } TES4Record attachedNameRecord = GetRecordByEDIDAndFollowNAME(edid); Nullable <int> scriptFormid = attachedNameRecord.TryGetSubrecordAsFormID("SCRI"); if (scriptFormid == null) { throw new ConversionException("Cannot resolve script type for " + edid + " - Asked base record has no script bound."); } string scriptRecordEDID = GetEDIDByFormID(scriptFormid.Value); TES5ScriptHeader scriptHeader = TES5ScriptHeaderFactory.GetFromCacheOrConstructByBasicType(scriptRecordEDID, TypeMapper.GetTES5BasicType(attachedNameRecord.RecordType), TES5TypeFactory.TES4Prefix, false); this.edidLowerCache.Add(edidLower, scriptHeader.ScriptType); return(scriptHeader.ScriptType); }
/* * TES5GlobalScope constructor. */ public TES5GlobalScope(TES5ScriptHeader scriptHeader) { this.ScriptHeader = scriptHeader; }
public void TrackRemoteScript(TES5ScriptHeader scriptHeader) { throw new ConversionException("Local variables cannot track remote scripts."); }
public override void TrackRemoteScript(TES5ScriptHeader scriptHeader) { throw new NotImplementedException(); }
public void TrackRemoteScript(TES5ScriptHeader scriptHeader) { throw new ConversionException("Cannot track TES5ScriptAsVariable as it tracks already."); }
public TES5ScriptAsVariable(TES5ScriptHeader scriptHeader) { this.scriptHeader = scriptHeader; }
public abstract void TrackRemoteScript(TES5ScriptHeader scriptHeader);
/* * Joins N QF subfragments into one QF fragment that can be properly binded into Skyrim VM * @throws ConversionException */ public TES5Target JoinQFFragments(IBuildTarget target, string resultingFragmentName, List <QuestStageScript> subfragmentsTrees) { int tes4FormID = GetTES4FormID(resultingFragmentName); StageMap stageMap = StageMapBuilder.Build(target, resultingFragmentName, esmAnalyzer, tes4FormID); /* * We need script fragment for objective handling for each stage, so when parsing the script fragments, * we"ll be marking them there, and intersecting this with stage. * This will give us an array of stages which don"t have script fragment, but will need it anyways * for objective handling. */ TES5ScriptHeader resultingScriptHeader = TES5ScriptHeaderFactory.GetFromCacheOrConstructByBasicType(resultingFragmentName, TES5BasicType.T_QUEST, TES5TypeFactory.TES4_Prefix, true); TES5GlobalScope resultingGlobalScope = new TES5GlobalScope(resultingScriptHeader); string[] referenceAliases = ReferenceAliasBuilder.Build(target, resultingFragmentName, esmAnalyzer, tes4FormID).ToArray(); foreach (string propertyName in referenceAliases) { resultingGlobalScope.AddProperty(TES5PropertyFactory.ConstructWithoutFormID(propertyName, TES5BasicType.T_REFERENCEALIAS, propertyName)); } List <QuestStageBlock> questStageBlocks = new List <QuestStageBlock>(); HashSet <int> implementedStages = new HashSet <int>(); HashSet <string> propertiesNamesDeclared = new HashSet <string>(); foreach (var subfragment in subfragmentsTrees) { TES5Target subfragmentsTree = subfragment.Script; TES5Script subfragmentScript = subfragmentsTree.Script; TES5GlobalScope subfragmentGlobalScope = subfragmentScript.GlobalScope; foreach (TES5Property subfragmentProperty in subfragmentGlobalScope.Properties) { /* * Move over the properties to the new global scope */ if (propertiesNamesDeclared.Add(subfragmentProperty.Name)) { resultingGlobalScope.AddProperty(subfragmentProperty); } else { if (subfragmentProperty.IsPlayerRef) { continue; } //WTM: Change: I don't think renaming these properties actually helps anything. /* * string propertyName = GeneratePropertyName(subfragmentScript.ScriptHeader, subfragmentProperty); * subfragmentProperty.Rename(propertyName); * if (!propertiesNamesDeclared.Add(subfragmentProperty.Name)) * { * throw new ConversionException(nameof(propertiesNamesDeclared) + " already contained property " + subfragmentProperty.Name + "."); * } */ //WTM: Change: I'm trying to unify properties and include extended type declarations. TES5Property existingProperty = resultingGlobalScope.GetPropertyByName(subfragmentProperty.Name); if (TES5InheritanceGraphAnalyzer.IsTypeOrExtendsType(existingProperty.TES5Type, subfragmentProperty.TES5Type)) { continue; } if (TES5InheritanceGraphAnalyzer.IsExtending(subfragmentProperty.TES5Type, existingProperty.TES5Type)) { existingProperty.TES5Type = subfragmentProperty.TES5Type; continue; } if (TES5InheritanceGraphAnalyzer.IsExtending(existingProperty.TES5Type, subfragmentProperty.TES5Type.NativeType)) { subfragmentProperty.TES5Type.NativeType = existingProperty.TES5Type.NativeType; existingProperty.TES5Type = subfragmentProperty.TES5Type; continue; } throw new ConversionException("Types were not compatible for property " + subfragmentProperty.Name + ": " + subfragmentProperty.TES5Type.Value + " should extend " + existingProperty.TES5Type.Value + " (" + existingProperty.TES5Type.NativeType.Value + ")."); } } List <ITES5CodeBlock> subfragmentBlocks = subfragmentScript.BlockList.Blocks; if (subfragmentBlocks.Count != 1) { throw new ConversionException("Wrong QF fragment, actual function count: " + subfragmentBlocks.Count + ".."); } ITES5CodeBlock subfragmentBlock = subfragmentBlocks[0]; if (subfragmentBlock.FunctionScope.BlockName != TES5FragmentFactory.GetFragmentName(0)) { throw new ConversionException("Wrong QF fragment funcname, actual function name: " + subfragmentBlock.FunctionScope.BlockName + "."); } string newFragmentFunctionName = TES5FragmentFactory.GetFragmentName(subfragment.Stage, subfragment.LogIndex); subfragmentBlock.FunctionScope.Rename(newFragmentFunctionName); List <int>?stageMapOfStage = stageMap.TryGetStageTargetsMap(subfragment.Stage); if (stageMapOfStage != null) { var objectiveCodeChunks = objectiveHandlingFactory.GenerateObjectiveHandling(subfragmentBlock, resultingGlobalScope, stageMapOfStage); foreach (var newCodeChunk in objectiveCodeChunks) { subfragmentBlock.AddChunk(newCodeChunk); } } questStageBlocks.Add(new QuestStageBlock(subfragment.Stage, subfragment.LogIndex, subfragmentBlock)); implementedStages.Add(subfragment.Stage); } /* * Diff to find stages which we still need to mark */ int[] nonDoneStages = stageMap.StageIDs.Where(stageID => !implementedStages.Contains(stageID)).ToArray(); foreach (int nonDoneStage in nonDoneStages) { TES5FunctionCodeBlock fragment = objectiveHandlingFactory.CreateEnclosedFragment(resultingGlobalScope, nonDoneStage, stageMap.GetStageTargetsMap(nonDoneStage)); questStageBlocks.Add(new QuestStageBlock(nonDoneStage, 0, fragment)); } this.mappedTargetsLogService.WriteScriptName(resultingFragmentName); foreach (var kvp in stageMap.MappedTargetsIndex) { var originalTargetIndex = kvp.Key; var mappedTargetIndexes = kvp.Value; this.mappedTargetsLogService.WriteLine(originalTargetIndex, mappedTargetIndexes); } TES5BlockList resultingBlockList = new TES5BlockList(questStageBlocks.OrderBy(b => b.StageID).ThenBy(b => b.LogIndex).Select(b => b.CodeBlock)); TES5Script resultingTree = new TES5Script(resultingGlobalScope, resultingBlockList, true); string outputPath = target.GetTranspileToPath(resultingFragmentName); return(new TES5Target(resultingTree, outputPath)); }
/* * TES5GlobalScope constructor. */ public TES5GlobalScope(TES5ScriptHeader scriptHeader) { this.ScriptHeader = scriptHeader; QuestHasOnUpdateRegisterForSingleUpdate = false; }
/* * Joins N QF subfragments into one QF fragment that can be properly binded into Skyrim VM * @throws ConversionException */ public TES5Target JoinQFFragments(BuildTarget target, string resultingFragmentName, List <QuestStageScript> subfragmentsTrees) { StageMap stageMap = BuildStageMap(target, resultingFragmentName); /* * We need script fragment for objective handling for each stage, so when parsing the script fragments, * we"ll be marking them there, and intersecting this with stage. * This will give us an array of stages which don"t have script fragment, but will need it anyways * for objective handling. */ TES5ScriptHeader resultingScriptHeader = new TES5ScriptHeader(resultingFragmentName, TES5BasicType.T_QUEST, "", true); TES5BlockList resultingBlockList = new TES5BlockList(); TES5GlobalScope resultingGlobalScope = new TES5GlobalScope(resultingScriptHeader); /* * Add ReferenceAlias"es * At some point, we might port the conversion so it doesn"t use the directly injected property, * but instead has a map to aliases and we"ll map accordingly and have references point to aliases instead */ string sourcePath = target.GetSourceFromPath(resultingFragmentName); string scriptName = Path.GetFileNameWithoutExtension(sourcePath); string aliasesFile = Path.Combine(Path.GetDirectoryName(sourcePath), scriptName + ".aliases"); string[] aliasesLines = File.ReadAllLines(aliasesFile); Dictionary <string, bool> aliasesDeclared = new Dictionary <string, bool>(); foreach (var alias in aliasesLines) { string trimmedAlias = alias.Trim(); if (trimmedAlias == "") { continue; } try { aliasesDeclared.Add(trimmedAlias, true); } catch (ArgumentException) { continue; } resultingGlobalScope.AddProperty(new TES5Property(trimmedAlias, TES5BasicType.T_REFERENCEALIAS, trimmedAlias)); } Dictionary <int, bool> implementedStages = new Dictionary <int, bool>(); Dictionary <string, bool> propertiesNamesDeclared = new Dictionary <string, bool>(); foreach (var subfragment in subfragmentsTrees) { TES5Target subfragmentsTree = subfragment.Script; TES5Script subfragmentScript = subfragmentsTree.Script; TES5GlobalScope subfragmentGlobalScope = subfragmentScript.GlobalScope; foreach (TES5Property subfragmentProperty in subfragmentGlobalScope.Properties) { /* * Move over the properties to the new global scope */ string propertyName; if (propertiesNamesDeclared.ContainsKey(subfragmentProperty.Name)) { propertyName = GeneratePropertyName(subfragmentScript.ScriptHeader, subfragmentProperty); subfragmentProperty.Rename(propertyName); } else { propertyName = subfragmentProperty.Name; } propertiesNamesDeclared.Add(propertyName, true); resultingGlobalScope.AddProperty(subfragmentProperty); //WTM: Note: See QF_FGD03Viranus_0102d154. Since ViranusDontonREF is present in multiple of the original fragments, //ViranusDontonREF gets renamed by the above. So multiple ViranusDontonREF variables are output. //Below I tried not renaming, assuming instead that variables with matching names and types within a set of fragments were intended to be the same variable. //It had OK results, but I'm leaving it commented for now. /*string propertyNameWithSuffix = subfragmentProperty.PropertyNameWithSuffix; * TES5Property existingProperty = resultingGlobalScope.Properties.Where(p => p.PropertyNameWithSuffix == propertyNameWithSuffix).FirstOrDefault(); * if (existingProperty != null && TES5InheritanceGraphAnalyzer.isExtending(subfragmentProperty.PropertyType, existingProperty.PropertyType)) * { * existingProperty.PropertyType = subfragmentProperty.PropertyType; * } * else * { * bool add = true; * if (existingProperty != null) * { * if (TES5InheritanceGraphAnalyzer.isExtending(existingProperty.PropertyType, subfragmentProperty.PropertyType)) * { * add = false; * } * else * { * string generatedPropertyName = generatePropertyName(subfragmentScript.ScriptHeader, subfragmentProperty, i); * subfragmentProperty.Rename(generatedPropertyName); * } * } * if (add) * { * resultingGlobalScope.Add(subfragmentProperty); * } * }*/ } List <ITES5CodeBlock> subfragmentBlocks = subfragmentScript.BlockList.Blocks; if (subfragmentBlocks.Count != 1) { throw new ConversionException("Wrong QF fragment, actual function count: " + subfragmentBlocks.Count + ".."); } ITES5CodeBlock subfragmentBlock = subfragmentBlocks[0]; if (subfragmentBlock.FunctionScope.BlockName != "Fragment_0") { throw new ConversionException("Wrong QF fragment funcname, actual function name: " + subfragmentBlock.FunctionScope.BlockName + ".."); } string newFragmentFunctionName = "Fragment_" + subfragment.Stage.ToString(); if (subfragment.LogIndex != 0) { newFragmentFunctionName += "_" + subfragment.LogIndex; } subfragmentBlock.FunctionScope.Rename(newFragmentFunctionName); var objectiveCodeChunks = this.objectiveHandlingFactory.GenerateObjectiveHandling(subfragmentBlock, resultingGlobalScope, stageMap.GetStageTargetsMap(subfragment.Stage)); foreach (var newCodeChunk in objectiveCodeChunks) { subfragmentBlock.AddChunk(newCodeChunk); } resultingBlockList.Add(subfragmentBlock); implementedStages[subfragment.Stage] = true; } /* * Diff to find stages which we still need to mark */ int[] nonDoneStages = stageMap.StageIDs.Where(stageID => !implementedStages.ContainsKey(stageID)).ToArray(); foreach (int nonDoneStage in nonDoneStages) { TES5FunctionCodeBlock fragment = this.objectiveHandlingFactory.CreateEnclosedFragment(resultingGlobalScope, nonDoneStage, stageMap.GetStageTargetsMap(nonDoneStage)); resultingBlockList.Add(fragment); } this.mappedTargetsLogService.WriteScriptName(resultingFragmentName); foreach (var kvp in stageMap.MappedTargetsIndex) { var originalTargetIndex = kvp.Key; var mappedTargetIndexes = kvp.Value; this.mappedTargetsLogService.WriteLine(originalTargetIndex, mappedTargetIndexes); } TES5Script resultingTree = new TES5Script(resultingGlobalScope, resultingBlockList); string outputPath = target.GetTranspileToPath(resultingFragmentName); return(new TES5Target(resultingTree, outputPath)); }
public TES5ScriptAsVariable(TES5ScriptHeader scriptHeader) : base("self") { this.scriptHeader = scriptHeader; }