/// <summary> /// Attempts to resolve tlk file conflicts between the module tlk and any /// tlk's defined in the hifs. It does this by attempting to build a /// new tlk file containing all of the tlk entries from all tlks. If there /// are no overlapping entries in the tlks's then this will succeed and /// the name of the new tlk will be returned, if there are overlaps then /// this will fail and string.Empty will be returned. /// </summary> /// <param name="hakInfos">The HIFs being added to the module</param> /// <param name="module">The module for which we are resolving conflicts</param> /// <param name="moduleInfo">The module info for the module</param> /// <param name="conflicts">The list of files in conflict</param> /// <returns>The name of the merge hak file, or string.Empty if a merge tlk /// could not be generated.</returns> public string ResolveConflicts(HakInfo[] hakInfos, Erf module, ModuleInfo moduleInfo, OverwriteWarningCollection conflicts) { try { // Reset the message shown flag so we show the message once. conflictHakMessageShown = false; // Generate the name of the conflict resolution hak and the directory // in which to place the files that will be added to the hak. conflictHak = GetFileName(module, "hak"); conflictHakDir = NWN.NWNInfo.GetFullFilePath(conflictHak) + ".temp"; OverwriteWarningCollection copy = conflicts.Clone(); foreach (OverwriteWarning conflict in copy) { // Check to see if we can attempt to resolve the conflict, if // we can then attempt to resolve it, and if the resolution is // successful then remove the conflict from the collection. switch (Path.GetExtension(conflict.File).ToLower()) { case ".2da": DisplayConflictHakMessage(module); if (Resolve2daConflict(hakInfos, module, moduleInfo, conflict)) conflicts.Remove(conflict); break; } } // Get all of the files in the conflict hak directory, if there are none // then there is no conflict hak. if (!Directory.Exists(conflictHakDir)) return string.Empty; string[] files = Directory.GetFiles(conflictHakDir); if (0 == files.Length) return string.Empty; // We have some resolved conflicts make the merge hak. Erf hak = Erf.New(Erf.ErfType.HAK, "Auto-generated merge hak"); foreach (string file in files) hak.AddFile(file, true); hak.SaveAs(NWN.NWNInfo.GetFullFilePath(conflictHak)); return conflictHak; } finally { if (Directory.Exists(conflictHakDir)) Directory.Delete(conflictHakDir, true); } }
/// <summary> /// Attempts to resolve conflicts for a 2da file. It does this be attempting to /// merge all duplicate copies of the 2da file into one merge 2da file. /// </summary> /// <param name="hakInfos">The HIFs being added to the module</param> /// <param name="module">The module</param> /// <param name="moduleInfo">The module info for the module</param> /// <param name="conflict">The 2da file in conflict</param> private bool Resolve2daConflict(HakInfo[] hakInfos, Erf module, ModuleInfo moduleInfo, OverwriteWarning conflict) { try { // Create an array list and get the 2da from the module, // adding it to the list if we get it. ArrayList list = new ArrayList(); _2DA twoDA = Get2da(moduleInfo, conflict.File); if (null != twoDA) list.Add(twoDA); // Now get all of the copies of the 2da from the various HIFs and // add them as well. foreach (HakInfo hakInfo in hakInfos) { twoDA = Get2da(hakInfo, conflict.File); if (null != twoDA) list.Add(twoDA); } // Load the BioWare version of the the 2da to use as a baseline, if the // file isn't in the bioware directory then we will have to make due w/o // it just make a blank 2da with the correct schema. _2DA bioware = LoadBioWare2da(conflict.File); // At this point we have all relevent copies of the conflicting 2da loaded into // memory, we now need to generate a merge 2da if possible. _2DA merge = Merge2das(bioware, list); if (null == merge) return false; // We have successfully merged all of the 2das, save the merge 2da and // return true. if (!Directory.Exists(conflictHakDir)) Directory.CreateDirectory(conflictHakDir); merge.SaveAs(Path.Combine(conflictHakDir, conflict.File)); return true; } catch (Exception) { return false; } }
/// <summary> /// Displays the building merge hak message once. /// </summary> /// <param name="module">The module that we are resolving conflicts for</param> private void DisplayConflictHakMessage(Erf module) { if (conflictHakMessageShown) return; // Let the user know we are building a merge tlk. progress.SetMessage("Building merge hak for module\n'{0}'.", Path.GetFileNameWithoutExtension(module.FileName)); conflictHakMessageShown = true; }
/// <summary> /// Generates a name for a conflict resolution file (hak or tlk). It generates /// a name that is currently unused on disk. /// </summary> /// <param name="module">The module for which to create a new tlk file</param> /// <param name="extension">The extension of the file to get a name for</param> /// <returns>The tlk file name, or null if the name could not be created</returns> private string GetFileName(Erf module, string extension) { // Use the first 12 characters of the module name as the base. Tlk // files can only have 16 character names max, and we want to save 4 // characters for the index. string namePrefix = Path.GetFileNameWithoutExtension(module.FileName); if (namePrefix.Length > 12) namePrefix = namePrefix.Substring(0, 12); namePrefix = namePrefix.Replace(" ", "_"); for (int i = 1; i <= 9999; i ++) { // Build the name using the name prefix and i, if the file name // does not exist in the tlk directory then return it. string name = string.Format("{0}{1:0000}.{2}", namePrefix, i, extension); if (!File.Exists(NWNInfo.GetFullFilePath(name))) return name; } return null; }
/// <summary> /// Decompresses an ERF file, returning the temp director that the /// file is decompressed to. /// </summary> /// <param name="erf">The erf to decompress</param> /// <param name="tempDirs">The string collection in which to place /// the temp directory.</param> /// <returns>The temp directory that the ERF was decompressed to, this /// is also added to the StringCollection</returns> private string Decompress(Erf erf, StringCollection tempDirs) { string tempDir = erf.FileName + ".Temp"; tempDirs.Add(tempDir); erf.Decompress(tempDir); return tempDir; }
/// <summary> /// Attempts to resolve tlk file conflicts between the module tlk and any /// tlk's defined in the hifs. It does this by attempting to build a /// new tlk file containing all of the tlk entries from all tlks. If there /// are no overlapping entries in the tlks's then this will succeed and /// the name of the new tlk will be returned, if there are overlaps then /// this will fail and string.Empty will be returned. /// </summary> /// <param name="module">The module for which we are resolving conflicts</param> /// <param name="hifTlks">The list of tlk files from the HIFs being /// installed.</param> /// <returns>The name of the merge tlk file, or string.Empty if a merge tlk /// could not be generated.</returns> public string ResolveTlkConflict(Erf module, string[] hifTlks) { try { // Let the user know we are building a merge tlk. progress.SetMessage("Building merge tlk for module\n'{0}'.", Path.GetFileNameWithoutExtension(module.FileName)); // Create an array to hold all of the tlk objects. Tlk[] tlks = new Tlk[hifTlks.Length]; // Load all of the tlk's. for (int i = 0; i < hifTlks.Length; i++) tlks[i] = Tlk.LoadTlk(NWNInfo.GetFullFilePath(hifTlks[i])); // Generate the name of the new tlk file. string newTlkFileName = GetFileName(module, "tlk"); if (null == newTlkFileName) throw new NWNException("Cannot create new tlk file for module {0}", module.FileName); // Get the largest entry count in all of the tlk files, we cannot move any of the tlk // entries from where they are so the new tlk file will have as many entries as the // largest source tlk file. int count = 0; foreach (Tlk tlk in tlks) if (tlk.Count > count) count = tlk.Count; // Create a new tlk file and add all of the entries from all of the tlk files // to it. Tlk newTlk = new Tlk(count); for (int i = 0; i < count; i++) { // Check to see which tlk file contains this entry. If multiple tlk // files contain this entry we cannot merge the tlk's Tlk.TlkEntry entry = null; foreach (Tlk tlk in tlks) { // Ignore empty entries. if (i >= tlk.Count || tlk.IsEmpty(i)) continue; // If we haven't gotten an entry for this row yet // then save this entry. If we have then we cannot // do the merge. if (null == entry) entry = tlk[i]; else { // Check to see if the data in two entries is the same. // If it is then both tlk files have the same string // data in the entry and we can still do the merge. This // is most likely to happen at index 0 where many tlk // files place "Bad Strref". if (0 == string.Compare(entry.Text, tlk[i].Text, true, CultureInfo.InvariantCulture)) continue; throw new InvalidOperationException(); } } // Save the entry in our new tlk file. if (null != entry) newTlk[i] = entry; } // Save the new tlk file and return it's file name. newTlk.SaveAs(NWN.NWNInfo.GetFullFilePath(newTlkFileName)); return newTlkFileName; } catch (InvalidOperationException) { // If an error occurs return string.Empty to indicate we couldn't generate // a merge tlk. return string.Empty; } }
/// <summary> /// This method creates a conflict collection from the module. /// </summary> /// <param name="module"></param> /// <returns></returns> private FileConflictCollection CreateConflictCollection(Erf module) { FileConflictCollection conflicts = new FileConflictCollection(); StringCollection replacedFiles = module.ReplacedFiles; foreach (string file in replacedFiles) { // Generate the full path of the module file, which has the same name // but is decompressed to the current temp directory. string moduleFile = Path.Combine(currentTempDir, Path.GetFileName(file)); // Create the conflict object and add it to the collection. FileConflict conflict = new FileConflict(moduleFile, file); conflicts.Add(conflict); } return conflicts; }
/// <summary> /// This method creates a script that calls ExecuteScript() to invoke /// multiple scripts. It is designed to allow multiple scripts to wire up /// to an event handler. The script is created and compiled and both /// the NSS and NCS files are added to the module. /// </summary> /// <param name="module">The module to modify</param> /// <param name="property">The property to which the scripts are being attached</param> /// <param name="originalScript">The original script on the property, or /// string.Empty if none</param> /// <param name="otherScripts">A list of other scripts to execute</param> /// <returns>The ResRef of the newly created script</returns> private string CreateExecuteScript(Erf module, string property, string originalScript, StringCollection otherScripts) { // If the original script and the otherScripts are the same then // there is nothing to do, just return originalScript as the RefRef if (1 == otherScripts.Count && 0 == string.Compare(otherScripts[0], originalScript, true, CultureInfo.InvariantCulture)) return originalScript.ToLower(); // Build the file name and full name of the script file. string substring = property.Length > 12 ? property.Substring(0, 12) : property; string sourceName = "hif_" + substring + ".nss"; string fullSourceName = Path.Combine(currentTempDir, sourceName); System.Text.StringBuilder b = new System.Text.StringBuilder(); // Check to see if the original script is one of our generated scripts // (the name will start with "hif_" if it is). If so then we need to // open the file and read the list of scripts currently being called // and add them to the list of scripts to call. StringCollection scriptsToExecute = new StringCollection(); bool createScript = 0 != string.Compare(originalScript, Path.GetFileNameWithoutExtension(sourceName), true, CultureInfo.InvariantCulture); if (!createScript) { // Read the list of scripts currently being executed from the hif_ // script file. string[] scripts = null; using (StreamReader reader = new StreamReader(fullSourceName)) { // Read the first line, strip the comment prefix off, and // split the line into all of the scripts that the script // executes. string line = reader.ReadLine(); line = line.Trim(); line = line.Substring(3, line.Length - 3); scripts = line.Split(','); } // Add all of the scripts currently in the file, and then add // all of the scripts in the otherScripts collection if they aren't // already there. scriptsToExecute.AddRange(scripts); foreach (string script in otherScripts) if (!scriptsToExecute.Contains(script)) scriptsToExecute.Add(script); } else { // Add the original script if there was on, then add all of the // other scripts to our execute list. if (string.Empty != originalScript) scriptsToExecute.Add(originalScript); foreach (string script in otherScripts) scriptsToExecute.Add(script); } // Create the script file. using (StreamWriter writer = new StreamWriter(fullSourceName, false, System.Text.Encoding.ASCII)) { // Make the first line be a list of the scripts being executed // so we can do updates to the file later. b.Length = 0; foreach (string script in scriptsToExecute) { if (b.Length > 0) b.Append(","); b.Append(script); } writer.WriteLine("// {0}", b.ToString()); // Write out a comment header. writer.WriteLine("/////////////////////////////////////////////////////////////////////"); writer.WriteLine("//"); writer.WriteLine("// This script has been auto-generated by HakInstaller to call"); writer.WriteLine("// multiple handlers for the {0} event.", property); writer.WriteLine("//"); writer.WriteLine("/////////////////////////////////////////////////////////////////////"); writer.WriteLine(""); writer.WriteLine("void main()"); writer.WriteLine("{"); // Add an execute line for each script in the collection. foreach (string script in scriptsToExecute) writer.WriteLine(" ExecuteScript(\"{0}\", OBJECT_SELF);", script); writer.WriteLine("}"); writer.Flush(); writer.Close(); } // Build the name of the obj file. string objName = Path.GetFileNameWithoutExtension(sourceName) + ".ncs"; string fullObjName = Path.Combine(currentTempDir, objName); // Generate the compiler command line. //string compiler = Path.Combine(NWNInfo.ToolsPath, "clcompile.exe"); // SBH for NWN2 // Use the PRC's compiler... its much better and can handle NWN2 string compiler = Path.Combine(NWNInfo.ToolsPath, "nwnnsscomp.exe"); b.Length = 0; // -c for compile, -I is the path to NWNSCRIPT.NSS 1 is the script to compile and 2 is the output directory for the ncs b.AppendFormat("-c -I \"{0}\" \"{1}\" \"{2}\"",NWNInfo.ToolsPath, fullSourceName, fullObjName); // Start the compiler process and wait for it. ProcessStartInfo info = new ProcessStartInfo(); info.FileName = compiler; info.Arguments = b.ToString(); info.CreateNoWindow = true; info.WindowStyle = ProcessWindowStyle.Hidden; Process process = Process.Start(info); process.WaitForExit(); // If the compiler didn't work then we have a problem. if (0 != process.ExitCode) throw new NWNException("Could not run the NWN script compiler"); // Add the source and object files to the module, if we have created new // files. If the original script was a hif_ script that we just changed // then the file is already part of the module, no need to add it. if (createScript) { module.AddFile(fullSourceName, true); module.AddFile(fullObjName, true); } // Return the ResRef for the new script file. return Path.GetFileNameWithoutExtension(sourceName).ToLower(); }
/// <summary> /// This function checks for hak conflicts, checking to see if any files /// in the hifs will overwrite files in the module or vica versa. If /// overwrites will happen, it prompts the user to see if we should continue, /// throwing an InstallCancelledException() if the user chooses to cancel. /// </summary> /// <param name="hakInfos">The hak infos being added to the module</param> /// <param name="decompressedErfs">The decompressed erfs</param> /// <param name="module">The module</param> /// <param name="moduleInfo">The module info</param> /// <param name="progress">The object implemening the progress interface</param> private void CheckForHakConflicts(HakInfo[] hakInfos, StringCollection decompressedErfs, Erf module, ModuleInfo moduleInfo, IHakInstallProgress progress) { // Create a hashtable for fast lookup and add all of the files in all // of the decompressed erf's to it. Hashtable hifErfHash = new Hashtable(10000); foreach(string directory in decompressedErfs) { // Strip the ".temp" off the end of the name. string erf = Path.GetFileNameWithoutExtension(directory); string[] files = Directory.GetFiles(directory); foreach (string file in files) { // Only add the ERF file if it's not already there. We assume that // the ERF's in the HIF play well together so we ignore duplicates. string key = Path.GetFileName(file).ToLower(); if ("exportinfo.gff" != key && !hifErfHash.Contains(key)) hifErfHash.Add(key, erf.ToLower()); } } // Build a list of all of the added haks. StringCollection hakInfoHaks = new StringCollection(); foreach (HakInfo hakInfo in hakInfos) { StringCollection temp = hakInfo.ModuleProperties["hak"] as StringCollection; if (null != temp) { foreach (string tempString in temp) hakInfoHaks.Add(tempString.ToLower()); } } // Add all of the files in all of the haks to the hash table. Hashtable hifHakHash = new Hashtable(10000); foreach (string hakName in hakInfoHaks) { Erf hak = Erf.Load(NWNInfo.GetFullFilePath(hakName)); StringCollection files = hak.Files; foreach (string file in files) try { string key = file.ToLower(); string hakNameLower = hakName.ToLower(); hifHakHash.Add(key, hakNameLower); } catch (ArgumentException) {} } // At this point we have built a lookup hash table that contains every // file going into the module (either directly in an erf or indirectly // in a hak). Now we need to loop through all of the files in the // module (and all of it's haks) and check to see if any of them are // going to get overwritten. At this point we have several cases. // 1. Module content is going to get replaced by erf content. We // do not handle that case now, we wait until the end and allow // the user to selectivly overwrite whatever they wish. // 2. Module content is going to get replaced by hak content. We must // warn the user that module files will not be used and the module // may not work. // 3. Module hak content is going to get replaced by hak content. Same // as above. // 4. Module hak content is going to overwrite erf content from the hif. // In this case the hif's content is the content that is going to be // ignored, again the user has to be warned. OverwriteWarningCollection hakWarnings = new OverwriteWarningCollection(); OverwriteWarningCollection erfWarnings = new OverwriteWarningCollection(); string moduleFileName = Path.GetFileName(module.FileName); // Loop through all of the files in the module checking to see if files in // added haks will overwrite them. StringCollection moduleFiles = module.Files; foreach (string file in moduleFiles) { string source = hifHakHash[file.ToLower()] as string; if (null != source) hakWarnings.Add(new OverwriteWarning(file.ToLower(), moduleFileName, source)); } // Loop through all of the files in the module's haks checking to see if // files in the added haks will overwrite them or if they will overwrite // files in added erf's. StringCollection moduleHaks = moduleInfo.Haks; foreach (string moduleHak in moduleHaks) { // Check to see if the hak in the module is one of the haks being added (this is // a no-op condition which will result in 100% duplicates, no need to check it). string hak = moduleHak + ".hak"; if (hakInfoHaks.Contains(hak.ToLower())) continue; Erf erf = Erf.Load(NWNInfo.GetFullFilePath(hak)); StringCollection hakFiles = erf.Files; foreach (string file in hakFiles) { // If the file is in the hak hash then it is going to be // overwritten by the hif's haks. string key = file.ToLower(); string source = hifHakHash[key] as string; if (null != source) hakWarnings.Add(new OverwriteWarning(key, Path.GetFileName(erf.FileName.ToLower()), source)); // If the file is in the erf hash then it will overwrite the // hif's erf. source = hifErfHash[key] as string; if (null != source) erfWarnings.Add(new OverwriteWarning(key, source, Path.GetFileName(erf.FileName.ToLower()))); } } // We have built the list of conflicts, before asking the user try to resolve the // conflicts as we may be able to generate a merge hak to resolve some of them. if (hakWarnings.Count > 0) { ConflictResolver resolver = new ConflictResolver(progress); mergeHak = resolver.ResolveConflicts(hakInfos, module, moduleInfo, hakWarnings); } // We have finished checking for files that are going to get overwritten. // If we have any warnings to issue to the user then do so now. if (hakWarnings.Count > 0 && !progress.ShouldOverwrite(hakWarnings, false, OverwriteWarningType.HifsOverwritesModule)) throw new InstallCancelledException(); if (erfWarnings.Count > 0 && !progress.ShouldOverwrite(erfWarnings, false, OverwriteWarningType.ModuleOverwritesHifs)) throw new InstallCancelledException(); }
/// <summary> /// This function checks for tlk conflicts, checking to see if the module /// and hifs have tlk files. If there are multiple tlk files it will attempt /// to generate a merge tlk file, if this cannot be done it will display an /// error message and throw an InstallCancelledException to cancel the install. /// </summary> /// <param name="hakInfos">The hak infos being added to the module</param> /// <param name="module">The module</param> /// <param name="moduleInfo">The module info</param> /// <param name="progress">The object implemening the progress interface</param> private void CheckForTlkConflicts(HakInfo[] hakInfos, Erf module, ModuleInfo moduleInfo, IHakInstallProgress progress) { // Create a tlk string collection and add the module's tlk if it has one. StringCollection tlks = new StringCollection(); if (string.Empty != moduleInfo.CustomTlk) tlks.Add(moduleInfo.CustomTlk.ToLower() + ".tlk"); // Add all of the tlk's from all of the HIFs. foreach (HakInfo hif in hakInfos) { StringCollection hifTlks = hif.ModuleProperties["customtlk"]; if (null != hifTlks && hifTlks.Count > 0) { // Loop through the tlk's individually to exclude duplicates. foreach (string hifTlk in hifTlks) { string lower = hifTlk.ToLower(); if (!tlks.Contains(lower)) tlks.Add(lower); } } } // If we have less than 2 tlks there is no conflict to resolve. if (tlks.Count < 2) return; // We have 2 or more tlk files, create a conflict resolver to // build a merge tlk file. ConflictResolver resolver = new ConflictResolver(progress); string[] tlkStrings = new string[tlks.Count]; tlks.CopyTo(tlkStrings, 0); mergeTlk = resolver.ResolveTlkConflict(module, tlkStrings); // If we don't get a merge tlk back from the conflict resolver then we couldn't // resolve the conflict. This is a fatal error so display an error message and // cancel the install. if (string.Empty == mergeTlk) { progress.DisplayErrorMessage("The module and custom content contain tlk files " + "that cannot be merged. The module update will be aborted."); throw new InstallCancelledException(); } // Save the merge tlk as the module's custom tlk. moduleInfo.CustomTlk = Path.GetFileNameWithoutExtension(mergeTlk.ToLower()); }
private void Module_SetEvent(Erf module, object source, string property, StringCollection values) { ModuleInfo moduleInfo = (ModuleInfo) source; // Get the current event handler on the module, if any. string currentEvent = moduleInfo[property]; // Get the total number of event handlers. int count = values.Count + (string.Empty == currentEvent ? 0 : 1); // If there is only 1 handler then this is easy, just set the handler. if (1 == count) { moduleInfo[property] = values[0]; return; } // There are multiple events, we must build a new script that // invokes the events. string newScript = CreateExecuteScript(module, property, currentEvent, values); moduleInfo[property] = newScript; }
private void Module_ScriptCache(Erf module, object source, string property, StringCollection values) { ModuleInfo moduleInfo = (ModuleInfo) source; // Copy the haks to a flat array and add them to the module. string[] scripts = new string[values.Count]; for (int i = 0; i < scripts.Length; i++) scripts[i] = (values[i].ToLower()); moduleInfo.AddToCache(scripts); }
private void Module_Hak(Erf module, object source, string property, StringCollection values) { ModuleInfo moduleInfo = (ModuleInfo) source; // Copy the haks to a flat array and add them to the module. string[] haks = new string[values.Count]; for (int i = 0; i < haks.Length; i++) haks[i] = Path.GetFileNameWithoutExtension(values[i].ToLower()); moduleInfo.AddHaks(haks); }
private void Module_CustomTlk(Erf module, object source, string property, StringCollection values) { // If we have a merge tlk then do not set the custom tlk, the // conflict resolution code for tlk's did that already. if (string.Empty != mergeTlk) return; string tlk = Path.GetFileNameWithoutExtension(values[0].ToLower()); // If the tlk is being set to the same tlk then do nothing. ModuleInfo moduleInfo = (ModuleInfo) source; if (0 == string.Compare(moduleInfo.CustomTlk, tlk, true, CultureInfo.InvariantCulture)) return; // Check to see if the module already has a custom tlk file, if it does // then we are dead; we cannot change it w/o breaking the module but the // hak won't run w/o it's custom tlk either. if (string.Empty != moduleInfo.CustomTlk) throw new NWNException( "The module {0} already contains a custom tlk {1}, hak cannot be added", moduleInfo.Name, values[0]); moduleInfo.CustomTlk = tlk; }
private void Module_Areas(Erf module, object source, string property, StringCollection values) { ModuleInfo moduleInfo = (ModuleInfo) source; // Copy the haks to a flat array and add them to the module. string[] areas = new string[values.Count]; for (int i = 0; i < areas.Length; i++) areas[i] = values[i].ToLower(); moduleInfo.AddAreas(areas); }