mysimulation/server/tso.files/Formats/IFF/Chunks/TREE.cs
Tony Bark 8fec258215 Added FSO.Files for use with the API server
Don't ask me. FreeSO is the prime example of dependency hell.
2024-05-01 04:38:12 -04:00

345 lines
12 KiB
C#
Executable file

using FSO.Files.Utils;
using System;
using System.Collections.Generic;
using System.IO;
namespace FSO.Files.Formats.IFF.Chunks
{
public class TREE : IffChunk
{
public static TREE GenerateEmpty(BHAV bhav)
{
var result = new TREE();
result.ChunkLabel = "";
result.ChunkID = bhav.ChunkID;
result.AddedByPatch = true;
result.ChunkProcessed = true;
result.RuntimeInfo = ChunkRuntimeState.Modified;
result.ChunkType = "TREE";
result.CorrectConnections(bhav);
return result;
/*
var additionID = bhav.Instructions.Length;
Func<byte, short> resolveTrueFalse = (byte pointer) =>
{
switch (pointer)
{
case 253:
return -1;
case 255:
//generate false
case 254:
//generate true
}
if (pointer == 255) return -1;
else if (pointer == 2)
};
//make an entry for each instruction. positions and sizes don't matter - we have a runtime flag to indicate they are not valid
for (int i=0; i<bhav.Instructions.Length; i++)
{
var inst = bhav.Instructions[i];
var box = new TREEBox(result);
box.InternalID = i;
box.PosisionInvalid = true;
box.Type = TREEBoxType.Primitive;
box.TruePointer =
}
*/
}
public List<TREEBox> Entries = new List<TREEBox>();
public int PrimitiveCount => Entries.FindLastIndex(x => x.Type == TREEBoxType.Primitive) + 1;
//runtime
public uint TreeVersion = 0;
public override void Read(IffFile iff, Stream stream)
{
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
{
var zero = io.ReadInt32();
var version = io.ReadInt32();
if (version > 1) throw new Exception("Unexpected TREE version: " + version);
string magic = io.ReadCString(4); //HBGN
if (magic != "EERT") throw new Exception("Magic number should be 'EERT', got " + magic);
var entryCount = io.ReadInt32();
Entries.Clear();
for (int i=0; i<entryCount; i++)
{
var box = new TREEBox(this);
box.Read(io, version);
box.InternalID = (short)i;
Entries.Add(box);
}
}
}
public override bool Write(IffFile iff, Stream stream)
{
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
{
io.WriteInt32(0);
io.WriteInt32(1);
io.WriteCString("EERT", 4);
io.WriteInt32(Entries.Count);
foreach (var entry in Entries)
{
entry.Write(io);
}
}
return true;
}
public void ApplyPointerDelta(int delta, int after)
{
foreach (var box in Entries)
{
if (box.InternalID >= after) box.InternalID += (short)delta;
if (box.TruePointer >= after) box.TruePointer += (short)delta;
if (box.FalsePointer >= after) box.FalsePointer += (short)delta;
}
}
public void CorrectConnections(BHAV bhav)
{
//make sure there are enough primitives for the bhav
var realPrimCount = bhav.Instructions.Length;
var treePrimCount = Entries.FindLastIndex(x => x.Type == TREEBoxType.Primitive) + 1;
ApplyPointerDelta(realPrimCount-treePrimCount, treePrimCount);
if (realPrimCount > treePrimCount)
{
//add new treeboxes
for (int i=treePrimCount; i<realPrimCount; i++)
{
var box = new TREEBox(this);
box.InternalID = (short)i;
box.PosisionInvalid = true;
box.Type = TREEBoxType.Primitive;
Entries.Insert(i, box);
}
}
else if (treePrimCount > realPrimCount)
{
//remove treeboxes
for (int i=treePrimCount; i>realPrimCount; i--)
{
Entries.RemoveAt(i-1);
}
}
//make sure connections for each of the primitives match the BHAV
//if they don't, reconnect them or generate new boxes (true/false endpoints, maybe gotos in future)
for (int i=0; i<realPrimCount; i++)
{
var prim = bhav.Instructions[i];
var box = Entries[i];
if (prim.TruePointer != GetTrueID(box.TruePointer))
{
box.TruePointer = GetCorrectBox(prim.TruePointer);
}
if (prim.FalsePointer != GetTrueID((short)box.FalsePointer))
{
box.FalsePointer = GetCorrectBox(prim.FalsePointer);
}
}
}
public void DeleteBox(TREEBox box)
{
//remove box. apply delta
var id = box.InternalID;
foreach (var box2 in Entries)
{
if (box2.TruePointer == id) box2.TruePointer = -1;
if (box2.FalsePointer == id) box2.FalsePointer = -1;
}
Entries.RemoveAt(id);
ApplyPointerDelta(-1, id);
}
public void InsertPrimitiveBox(TREEBox box)
{
var primEnd = PrimitiveCount;
ApplyPointerDelta(1, primEnd);
box.InternalID = (short)primEnd;
Entries.Insert(primEnd, box);
}
public void InsertRemovedBox(TREEBox box)
{
var oldIndex = box.InternalID;
ApplyPointerDelta(1, oldIndex);
Entries.Insert(oldIndex, box);
}
public TREEBox MakeNewPrimitiveBox(TREEBoxType type)
{
var primEnd = PrimitiveCount;
ApplyPointerDelta(1, primEnd);
//find end of primitives and add box there. apply delta
var box = new TREEBox(this);
box.InternalID = (short)primEnd;
box.PosisionInvalid = true;
box.Type = type;
Entries.Insert(primEnd, box);
return box;
}
public TREEBox MakeNewSpecialBox(TREEBoxType type)
{
//add box at end. no delta needs to be applied.
var box = new TREEBox(this);
box.InternalID = (short)Entries.Count;
box.PosisionInvalid = true;
box.Type = type;
Entries.Add(box);
return box;
}
private short GetCorrectBox(byte realID)
{
switch (realID)
{
case 255:
//create false box
var f = MakeNewSpecialBox(TREEBoxType.False);
return f.InternalID;
case 254:
//create true box
var t = MakeNewSpecialBox(TREEBoxType.True);
return t.InternalID;
case 253:
return -1;
default:
return realID;
}
}
public byte GetTrueID(short boxID)
{
return GetBox(boxID)?.TrueID ?? 253;
}
public TREEBox GetBox(short pointer)
{
if (pointer < 0 || pointer >= Entries.Count) return null;
return Entries[pointer];
}
}
public class TREEBox
{
//runtime
public short InternalID = -1;
public bool PosisionInvalid; //forces a regeneration of position using the default tree algorithm
public TREE Parent;
public byte TrueID
{
get
{
switch (Type)
{
case TREEBoxType.Primitive:
return (byte)InternalID;
case TREEBoxType.Goto:
return LabelTrueID(new HashSet<short>());
case TREEBoxType.Label:
return 253; //arrows cannot point to a label
case TREEBoxType.True:
return 254;
case TREEBoxType.False:
return 255;
}
return 253;
}
}
public byte LabelTrueID(HashSet<short> visited)
{
if (Type != TREEBoxType.Goto) return TrueID;
if (visited.Contains(InternalID)) return 253; //error
visited.Add(InternalID);
return Parent?.GetBox(Parent.GetBox(TruePointer)?.TruePointer ?? -1)?.LabelTrueID(visited) ?? 253;
}
//data
public TREEBoxType Type;
public ushort Unknown;
public short Width;
public short Height;
public short X;
public short Y;
public short CommentSize = 0x10;
public short TruePointer = -1;
public short Special; //0 or -1... unknown.
public int FalsePointer = -1;
public string Comment = "";
public int TrailingZero = 0;
public TREEBox(TREE parent)
{
Parent = parent;
}
public void Read(IoBuffer io, int version)
{
Type = (TREEBoxType)io.ReadUInt16();
Unknown = io.ReadUInt16();
Width = io.ReadInt16();
Height = io.ReadInt16();
X = io.ReadInt16();
Y = io.ReadInt16();
CommentSize = io.ReadInt16();
TruePointer = io.ReadInt16();
Special = io.ReadInt16();
FalsePointer = io.ReadInt32();
Comment = io.ReadNullTerminatedString();
if (Comment.Length % 2 == 0) io.ReadByte(); //padding to 2 byte align
if (version > 0) TrailingZero = io.ReadInt32();
if (!Enum.IsDefined(typeof(TREEBoxType), Type)) throw new Exception("Unexpected TREE box type: " + Type.ToString());
if (Special < -1 || Special > 0) throw new Exception("Unexpected TREE special: " + Special);
if (Unknown != 0) throw new Exception("Unexpected Unknown: " + Unknown);
if (TrailingZero != 0) Console.WriteLine("Unexpected TrailingZero: " + TrailingZero);
}
public void Write(IoWriter io)
{
io.WriteUInt16((ushort)Type);
io.WriteUInt16(Unknown);
io.WriteInt16(Width);
io.WriteInt16(Height);
io.WriteInt16(X);
io.WriteInt16(Y);
io.WriteInt16(CommentSize);
io.WriteInt16(TruePointer);
io.WriteInt16(Special);
io.WriteInt32(FalsePointer);
io.WriteCString(Comment);
if (Comment.Length % 2 == 0) io.WriteByte(0xCD); //padding to 2 byte align
io.WriteInt32(TrailingZero);
}
public override string ToString()
{
return Type.ToString() + " (" + TruePointer + ((FalsePointer == -1) ? "" : ("/"+FalsePointer)) + "): " + Comment;
}
}
public enum TREEBoxType : ushort
{
Primitive = 0,
True = 1,
False = 2,
Comment = 3,
Label = 4,
Goto = 5 //no comment size, roughly primitive sized (180, 48), pointer goes to Label
}
}