using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.ComponentModel; namespace FSO.Files.Formats.tsodata { public class TSODataDefinition { public List List1; public List List2; public List List3; public List Strings; public Struct[] Structs; public DerivedStruct[] DerivedStructs; public uint FileID; public static TSODataDefinition Active; private Dictionary StructsByName = new Dictionary(); public void Read(Stream stream) { using (var reader = new BinaryReader(stream)) { FileID = reader.ReadUInt32(); this.List1 = ReadList(reader, false); this.List2 = ReadList(reader, false); this.List3 = ReadList(reader, true); var numStrings = reader.ReadUInt32(); this.Strings = new List(); for (var i = 0; i < numStrings; i++) { var stringEntry = new StringTableEntry(); stringEntry.ID = reader.ReadUInt32(); stringEntry.Value = ReadNullTerminatedString(reader); stringEntry.Category = (StringTableType)reader.ReadByte(); this.Strings.Add(stringEntry); } } Activate(); } public void Write(Stream stream) { using (var writer = new BinaryWriter(stream)) { writer.Write(FileID); WriteList(List1, writer, false); WriteList(List2, writer, false); WriteList(List3, writer, true); writer.Write(Strings.Count); foreach (var str in Strings) { writer.Write(str.ID); writer.Write(Encoding.ASCII.GetBytes(str.Value)); writer.Write((byte)0); writer.Write((byte)str.Category); } } } public void Activate() { var Structs = new List(); var DerivedStructs = new List(); //1st level structs. all fields are primitive types foreach (var item in List1) { var fields = new List(); foreach (var field in item.Entries) { if (field.TypeStringID == 0xA99AF3AC) Console.WriteLine("unknown value: " + GetString(item.NameStringID) + "::" + GetString(field.NameStringID)); fields.Add(new StructField { ID = field.NameStringID, Name = GetString(field.NameStringID), TypeID = field.TypeStringID, Classification = (StructFieldClassification)field.TypeClass, ParentID = item.NameStringID }); } Structs.Add(new Struct { ID = item.NameStringID, Name = GetString(item.NameStringID), Fields = fields.ToList() }); } //2nd level structs. fields can be first level structs //note: this might be a hard limit in tso, but it is not particularly important in freeso // the game will even behave correctly if a 2nd level references another 2nd level, // though a circular reference will likely still break everything. foreach (var item in List2) { var fields = new List(); foreach (var field in item.Entries) { if (field.TypeStringID == 0xA99AF3AC) Console.WriteLine("unknown value: " + GetString(item.NameStringID) + "::" + GetString(field.NameStringID)); fields.Add(new StructField { ID = field.NameStringID, Name = GetString(field.NameStringID), TypeID = field.TypeStringID, Classification = (StructFieldClassification)field.TypeClass, ParentID = item.NameStringID }); } Structs.Add(new Struct { ID = item.NameStringID, Name = GetString(item.NameStringID), Fields = fields.ToList() }); } //derived structs. serve as valid subsets of fields of an entity that the client can request. foreach (var item in List3) { var fields = new List(); foreach (var field in item.Entries) { if (field.TypeStringID == 0xA99AF3AC) Console.WriteLine("unknown value: " + GetString(item.NameStringID) + "::" + GetString(field.NameStringID)); fields.Add(new DerivedStructFieldMask { ID = field.NameStringID, Name = GetString(field.NameStringID), Type = (DerivedStructFieldMaskType)field.TypeClass }); } DerivedStructs.Add(new DerivedStruct { ID = item.NameStringID, Parent = item.ParentStringID, Name = GetString(item.NameStringID), FieldMasks = fields.ToArray() }); } this.Structs = Structs.ToArray(); this.DerivedStructs = DerivedStructs.ToArray(); foreach (var _struct in Structs) { StructsByName.Add(_struct.Name, _struct); } //InjectStructs(); Active = this; } private void InjectStructs() { //this is just an example of how to do this. //todo: a format we can easily create and read from to provide these new fields StructsByName["Lot"].Fields.Add(new StructField() { Name = "Lot_SkillGamemode", ID = 0xaabbccdd, Classification = StructFieldClassification.SingleField, ParentID = StructsByName["Lot"].ID, TypeID = 1768755593 //uint32 }); var fields = DerivedStructs[17].FieldMasks.ToList(); fields.Add(new DerivedStructFieldMask() { ID = 0xaabbccdd, Name = "Lot_SkillGamemode", Type = DerivedStructFieldMaskType.KEEP }); DerivedStructs[17].FieldMasks = fields.ToArray(); } public Struct GetStructFromValue(object value) { if (value == null) { return null; } return GetStruct(value.GetType()); } public Struct GetStruct(Type type) { return GetStruct(type.Name); } public Struct GetStruct(string name) { if (StructsByName.ContainsKey(name)) { return StructsByName[name]; } return null; } public Struct GetStruct(uint id) { return Structs.FirstOrDefault(x => x.ID == id); } public StringTableType GetStringType(uint id) { var item = Strings.FirstOrDefault(x => x.ID == id); if (item == null) { return StringTableType.Field; } return item.Category; } public string GetString(uint id) { var item = Strings.FirstOrDefault(x => x.ID == id); if (item == null) { return null; } return item.Value; } public void SetString(uint id, string value) { var item = Strings.FirstOrDefault(x => x.ID == id); if (item == null) { return; } item.Value = value; } public void RemoveString(uint id) { Strings.RemoveAll(x => x.ID == id); } public string GetString(List strings, uint id) { var item = strings.FirstOrDefault(x => x.ID == id); if (item == null) { return ""; } return item.Value; } private string ReadNullTerminatedString(BinaryReader reader) { var result = ""; while (true) { var ch = (char)reader.ReadByte(); if (ch == '\0') { break; } else { result += ch; } } return result; } private List ReadList(BinaryReader reader, bool parentID) { var list1Count = reader.ReadUInt32(); var list1 = new List(); for (int i = 0; i < list1Count; i++) { var entry = new ListEntry(); entry.Parent = this; entry.NameStringID = reader.ReadUInt32(); if (parentID) { entry.ParentStringID = reader.ReadUInt32(); } entry.Entries = new List(); var subEntryCount = reader.ReadUInt32(); for (int y = 0; y < subEntryCount; y++) { var subEntry = new ListEntryEntry(); subEntry.Parent = entry; subEntry.NameStringID = reader.ReadUInt32(); subEntry.TypeClass = reader.ReadByte(); if (!parentID) { subEntry.TypeStringID = reader.ReadUInt32(); } entry.Entries.Add(subEntry); } list1.Add(entry); } return list1; } private void WriteList(List list, BinaryWriter writer, bool parentID) { writer.Write(list.Count); foreach (var entry in list) { writer.Write(entry.NameStringID); if (parentID) writer.Write(entry.ParentStringID); writer.Write(entry.Entries.Count); foreach (var subEntry in entry.Entries) { writer.Write(subEntry.NameStringID); writer.Write(subEntry.TypeClass); if (!parentID) writer.Write(subEntry.TypeStringID); } } } } public enum StringTableType : byte { Field = 1, Primitive = 2, Level1 = 3, Level2 = 4, Derived = 5 } public class StringTableEntry { public uint ID; public string Value; public StringTableType Category; } public class ListEntry { public TSODataDefinition Parent; public uint NameStringID { get; set; } public uint ParentStringID { get; set; } public List Entries; } public class ListEntryEntry { public ListEntry Parent; public uint NameStringID; public byte TypeClass; public uint TypeStringID; [Category("Struct Properties")] [Description("The name for this field/struct. (ONLY FOR STRUCTS)")] public string Name { get { return Parent.Parent.GetString(NameStringID); } set { Parent.Parent.SetString(NameStringID, value); } } [Category("Struct Properties")] [Description("The type this field should have. (ONLY FOR STRUCTS)")] [TypeConverter(typeof(TypeSelector))] public string FieldType { get { return Parent.Parent.GetString(TypeStringID); } set { TypeStringID = Parent.Parent.Strings.First(x => x.Value == value).ID; } } [Category("Struct Properties")] [Description("What kind of collection this field is. (ONLY FOR STRUCTS)")] public StructFieldClassification FieldClass { get { return (StructFieldClassification)TypeClass; } set { TypeClass = (byte)value; } } private class TypeSelector : TypeConverter { public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { return new StandardValuesCollection( TSODataDefinition.Active.Strings .Where(x => x.Category == StringTableType.Level1 || x.Category == StringTableType.Primitive) .OrderBy(x => x.Category) .ThenBy(x => x.Value) .Select(x => x.Value) .ToList() ); } } [Category("Mask Properties")] [TypeConverter(typeof(NameSelector))] [Description("The field to mask. (ONLY FOR MASKS)")] public string MaskField { get { return Parent.Parent.GetString(NameStringID); } set { NameStringID = Parent.Parent.Strings.First(x => x.Value == value).ID; } } [Category("Mask Properties")] [Description("If this field should be kept or removed for this request. (ONLY FOR MASKS)")] public DerivedStructFieldMaskType MaskMode { get { return (DerivedStructFieldMaskType)TypeClass; } set { TypeClass = (byte)value; } } private class NameSelector : TypeConverter { public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { return new StandardValuesCollection( TSODataDefinition.Active.Strings .Where(x => x.Category == StringTableType.Field) .OrderBy(x => x.Category) .ThenBy(x => x.Value) .Select(x => x.Value) .ToList() ); } } } public class Struct { public uint ID; public string Name; public List Fields; } public class StructField { public uint ID; public string Name; public StructFieldClassification Classification; public uint TypeID; public uint ParentID; } public enum StructFieldClassification { SingleField = 0, Map = 1, List = 2 } public class DerivedStruct { public uint ID; public string Name; public uint Parent; public DerivedStructFieldMask[] FieldMasks; } public class DerivedStructFieldMask { public uint ID; public string Name; public DerivedStructFieldMaskType Type; } public enum DerivedStructFieldMaskType { KEEP = 0x01, REMOVE = 0x02 } }