/// <summary> /// Merges 2 tlk objects, saving the results in the specified tlk file. The merge tlk file /// is just added to the end of the source tlk file. Unlike 2da merging, the merge tlk /// does not overwrite any entries in the source tlk. Tlk entries are not row critical like /// 2da rows (since tlk strings are not saved in character files), so exact positioning of /// them is not as critical. /// </summary> /// <param name="source">The source tlk</param> /// <param name="merge">The merge tlk</param> /// <param name="outFile">The name of the output tlk file</param> /// <returns>The offset of the first entry of the merge tlk in the output file. This offset /// can be used to fixup 2da entries that refer to the merge tlk.</returns> public static int MergeTlk(Tlk source, Tlk merge, string outFile) { /* // Open the output tlk. using (FileStream writer = new FileStream(outFile, FileMode.Create, FileAccess.Write, FileShare.None)) { // Build a RawResRef array containing both the source and merge RawResRef arrays. Then // loop through all of the merge entries and fixup the string offsets to point // past the source data to the merge data, which we will glue on the end of the // source data. RawResRef[] outResRefs = new RawResRef[source.resRefs.Length + merge.resRefs.Length]; source.resRefs.CopyTo(outResRefs, 0); merge.resRefs.CopyTo(outResRefs, source.resRefs.Length); for (int i = source.resRefs.Length; i < outResRefs.Length; i++) { if (0 != (outResRefs[i].flags & (int) ResRefFlags.textPresent)) outResRefs[i].offsetToString += source.stringBytes.Length; } // Build a header with a string count of all of the source + merge strings, then // write it out. TlkHeader headerOut = source.header; headerOut.stringCount += merge.header.stringCount; headerOut.stringOffset = headerSize + (outResRefs.Length * RawResRefSize); byte[] headerBytes = RawSerialize(headerOut); writer.Write(headerBytes, 0, headerBytes.Length); // Write the RawResRef data. for (int i = 0; i < outResRefs.Length; i++) { byte[] bytes = RawSerialize(outResRefs[i]); writer.Write(bytes, 0, bytes.Length); } // Write the source and merge string data out to the file. writer.Write(source.stringBytes, 0, source.stringBytes.Length); writer.Write(merge.stringBytes, 0, merge.stringBytes.Length); writer.Flush(); writer.Close(); } // Return the number of strings in the source tlk. Since we glued the merge // tlk onto the end of the source tlk, this value will be the fixup value we need // to correct 2da tlk references. return source.header.stringCount; */ return 0; }
/// <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> /// Creates a Tlk object for the specified tlk file. /// </summary> /// <param name="fileName">The tlk file</param> /// <returns>A Tlk object representing the tlk file</returns> public static Tlk LoadTlk(string fileName) { // Open the tlk file. Tlk tlk = new Tlk(); using (FileStream reader = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { // Save the name of the tlk file. FileInfo info = new FileInfo(fileName); tlk.name = info.Name; // Read the header and decode it. byte[] buffer = new byte[Tlk.headerSize]; if (reader.Read(buffer, 0, buffer.Length) != buffer.Length) ThrowException("Tlk file {0} is corrupt", fileName); tlk.DeserializeHeader(buffer); // Do a reality check on the tlk file. if (tlk.header.fileType != tlkFile) ThrowException("{0} is not a tlk file", fileName); if (tlk.header.fileVersion != tlkVersion) ThrowException("{0} is an unsupported tlk file", fileName); // Read the RawResRef array and decode it. int size = tlk.header.stringCount * Tlk.RawResRefSize; buffer = new byte[size]; if (reader.Read(buffer, 0, buffer.Length) != buffer.Length) ThrowException("Tlk file {0} is corrupt", fileName); tlk.DeserializeRawResRefs(buffer); // Read the raw string data. buffer = new byte[reader.Length - tlk.header.stringOffset]; if (reader.Read(buffer, 0, buffer.Length) != buffer.Length) ThrowException("Tlk file {0} is corrupt", fileName); // Load the strings from the raw bytes into our string array. tlk.strings = new string[tlk.header.stringCount]; for (int i = 0; i < tlk.header.stringCount; i++) tlk.strings[i] = tlk.GetStringFromBuffer(buffer, i); } return tlk; }