mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-03-21 09:11:20 +00:00
517 lines
16 KiB
C#
Executable file
517 lines
16 KiB
C#
Executable file
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<ListEntry> List1;
|
|
public List<ListEntry> List2;
|
|
public List<ListEntry> List3;
|
|
public List<StringTableEntry> Strings;
|
|
|
|
public Struct[] Structs;
|
|
public DerivedStruct[] DerivedStructs;
|
|
public uint FileID;
|
|
|
|
public static TSODataDefinition Active;
|
|
|
|
private Dictionary<string, Struct> StructsByName = new Dictionary<string, Struct>();
|
|
|
|
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<StringTableEntry>();
|
|
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<Struct>();
|
|
var DerivedStructs = new List<DerivedStruct>();
|
|
|
|
//1st level structs. all fields are primitive types
|
|
foreach (var item in List1)
|
|
{
|
|
var fields = new List<StructField>();
|
|
|
|
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<StructField>();
|
|
|
|
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<DerivedStructFieldMask>();
|
|
|
|
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<StringTableEntry> 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<ListEntry> ReadList(BinaryReader reader, bool parentID)
|
|
{
|
|
var list1Count = reader.ReadUInt32();
|
|
|
|
var list1 = new List<ListEntry>();
|
|
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<ListEntryEntry>();
|
|
|
|
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<ListEntry> 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<ListEntryEntry> 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<StructField> 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
|
|
}
|
|
}
|