using System; using System.Collections.Generic; using System.Linq; using System.IO; using FSO.Files.Utils; namespace FSO.Files.Formats.IFF.Chunks { /// /// This chunk type holds text strings. /// The first two bytes correspond to the format code, of which there are four types. /// Some chunks in the game do not specify any data after the version number, so be sure to implement bounds checking. /// public class STR : IffChunk { public static string[] LanguageSetNames = { "English (US)", "English (UK)", "French", "German", "Italian", "Spanish", "Dutch", "Danish", "Swedish", "Norwegian", "Finish", "Hebrew", "Russian", "Portuguese", "Japanese", "Polish", "Simplified Chinese", "Traditional Chinese", "Thai", "Korean", "Slovak" }; public STRLanguageSet[] LanguageSets = new STRLanguageSet[20]; public static STRLangCode DefaultLangCode = STRLangCode.EnglishUS; public STR() { for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] }; } /// /// How many strings are in this chunk? /// public int Length { get { return LanguageSets[0]?.Strings.Length ?? 0; } } public STRLanguageSet GetLanguageSet(STRLangCode set) { if (set == STRLangCode.Default) set = DefaultLangCode; int code = (int)set; if ((LanguageSets[code-1]?.Strings.Length ?? 0) == 0) return LanguageSets[0]; //if undefined, fallback to English US else return LanguageSets[code-1]; } public bool IsSetInit(STRLangCode set) { if (set == STRLangCode.Default) set = DefaultLangCode; if (set == STRLangCode.EnglishUS) return true; int code = (int)set; return (LanguageSets[code - 1].Strings.Length > 0); } public void InitLanguageSet(STRLangCode set) { if (set == STRLangCode.Default) set = DefaultLangCode; int code = (int)set; var length = LanguageSets[0].Strings.Length; LanguageSets[code - 1].Strings = new STRItem[length]; for (int i=0; i< length; i++) { var src = LanguageSets[0].Strings[i]; LanguageSets[code - 1].Strings[i] = new STRItem() { LanguageCode = (byte)code, Value = src.Value, Comment = src.Comment }; } } /// /// Gets a string from this chunk. /// /// Index of string. /// A string at specific index, null if not found. /// public string GetString(int index) { return GetString(index, STRLangCode.Default); } public string GetString(int index, STRLangCode language) { var item = GetStringEntry(index, language); if (item != null) { return item.Value; } return null; } public string GetComment(int index) { return GetComment(index, STRLangCode.Default); } public string GetComment(int index, STRLangCode language) { var item = GetStringEntry(index, language); if (item != null) { return item.Comment; } return null; } public void SetString(int index, string value) { SetString(index, value, STRLangCode.Default); } public void SetString(int index, string value, STRLangCode language) { var languageSet = GetLanguageSet(language); if (index < languageSet.Strings.Length) { languageSet.Strings[index].Value = value; } } public void SwapString(int srcindex, int dstindex) { foreach (var languageSet in LanguageSets) { if (languageSet.Strings.Length == 0) continue; //language not initialized var temp = languageSet.Strings[srcindex]; languageSet.Strings[srcindex] = languageSet.Strings[dstindex]; languageSet.Strings[dstindex] = temp; } } public void InsertString(int index, STRItem item) { byte i = 1; foreach (var languageSet in LanguageSets) { if (languageSet.Strings.Length == 0 && i > 1) { i++; continue; //language not initialized } var newStr = new STRItem[languageSet.Strings.Length + 1]; Array.Copy(languageSet.Strings, newStr, index); //copy before strings newStr[index] = new STRItem() { LanguageCode = i, Value = item.Value, Comment = item.Comment }; Array.Copy(languageSet.Strings, index, newStr, index + 1, (languageSet.Strings.Length - index)); languageSet.Strings = newStr; i++; } } public void RemoveString(int index) { foreach (var languageSet in LanguageSets) { if (languageSet.Strings.Length == 0) continue; //language not initialized var newStr = new STRItem[languageSet.Strings.Length - 1]; Array.Copy(languageSet.Strings, newStr, index); //copy before strings Array.Copy(languageSet.Strings, index + 1, newStr, index, (languageSet.Strings.Length - (index + 1))); languageSet.Strings = newStr; } } /// /// Gets a STRItem instance from this STR chunk. /// /// Index of STRItem. /// STRItem at index, null if not found. public STRItem GetStringEntry(int index) { return GetStringEntry(index, STRLangCode.Default); } public STRItem GetStringEntry(int index, STRLangCode language) { var languageSet = GetLanguageSet(language); if (index < (languageSet?.Strings.Length ?? 0) && index > -1) { return languageSet.Strings[index]; } return null; } /// /// Reads a STR chunk from a stream. /// /// An Iff instance. /// A Stream object holding a STR chunk. public override void Read(IffFile iff, Stream stream) { using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) { var formatCode = io.ReadInt16(); LanguageSets = new STRLanguageSet[20]; if (!io.HasMore){ for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] }; return; } if (formatCode == 0) { var numStrings = io.ReadUInt16(); for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] }; LanguageSets[0].Strings = new STRItem[numStrings]; for (var i = 0; i < numStrings; i++) { LanguageSets[0].Strings[i] = new STRItem { Value = io.ReadPascalString() }; } } //This format changed 00 00 to use C strings rather than Pascal strings. else if (formatCode == -1) { var numStrings = io.ReadUInt16(); for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] }; LanguageSets[0].Strings = new STRItem[numStrings]; for (var i = 0; i < numStrings; i++) { LanguageSets[0].Strings[i] = new STRItem { Value = io.ReadNullTerminatedUTF8() }; } } //This format changed FF FF to use string pairs rather than single strings. else if (formatCode == -2) { var numStrings = io.ReadUInt16(); for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] }; LanguageSets[0].Strings = new STRItem[numStrings]; for (var i = 0; i < numStrings; i++) { LanguageSets[0].Strings[i] = new STRItem { Value = io.ReadNullTerminatedString(), Comment = io.ReadNullTerminatedString() }; } } //This format changed FD FF to use a language code. else if (formatCode == -3) { var numStrings = io.ReadUInt16(); for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] }; List[] LangSort = new List[20]; for (var i = 0; i < numStrings; i++) { var item = new STRItem { LanguageCode = io.ReadByte(), Value = io.ReadNullTerminatedString(), Comment = io.ReadNullTerminatedString() }; var lang = item.LanguageCode; if (lang == 0) lang = 1; else if (lang < 0 || lang > 20) continue; //??? if (LangSort[lang - 1] == null) { LangSort[lang-1] = new List(); } LangSort[lang - 1].Add(item); } for (int i=0; i x?.Strings?.Length ?? 0); io.WriteInt16(total); foreach (var set in LanguageSets) { if (set?.Strings != null) { foreach (var str in set.Strings) { io.WriteByte((byte)(str.LanguageCode)); io.WriteNullTerminatedString(str.Value); io.WriteNullTerminatedString(str.Comment); } } } for (int i=0; i /// Item in a STR chunk. /// public class STRItem { public byte LanguageCode; public string Value; public string Comment; public STRItem() { } public STRItem(string value) { Value = value; Comment = ""; } } public enum STRLangCode : byte { Default = 0, EnglishUS = 1, EnglishUK = 2, French = 3, German = 4, Italian = 5, Spanish = 6, Dutch = 7, Danish = 8, Swedish = 9, Norwegian = 10, Finish = 11, Hebrew = 12, Russian = 13, Portuguese = 14, Japanese = 15, Polish = 16, SimplifiedChinese = 17, TraditionalChinese = 18, Thai = 19, Korean = 20, //begin freeso Slovak = 21 } /// /// Set of STRItems for a language. /// public class STRLanguageSet { public STRItem[] Strings = new STRItem[0]; } }