using Cysharp.Collections; using IFPSLib.Types; using System; using System.Collections.Generic; using System.IO; using System.Text; namespace IFPSLib.Emit { namespace FDecl { public abstract class Base { internal bool HasReturnArgument; /// /// If null, argument information is unknown. /// internal IList Arguments { get; set; } internal virtual string Name => ""; public abstract int Size { get; } protected const string DllString = "dll:"; protected const string ClassString = "class:"; protected const string ComString = "intf:."; internal static Base Load(BinaryReader br, Script script) { var fdeclLen = br.Read(); using (var fdeclMem = new NativeMemoryArray(fdeclLen, true)) { var fdeclSpan = fdeclMem.AsSpan(); br.Read(fdeclSpan); using (var brDecl = new BinaryReader(fdeclMem.AsStream(), Encoding.UTF8, true)) { if (fdeclSpan.EqualsAsciiString(0, DllString)) { brDecl.BaseStream.Position = DllString.Length; return DLL.Load(brDecl, script); } else if (fdeclSpan.EqualsAsciiString(0, ClassString)) { brDecl.BaseStream.Position = ClassString.Length; return Class.Load(brDecl, script); } else if (fdeclSpan.EqualsAsciiString(0, ComString)) { brDecl.BaseStream.Position = ComString.Length; return COM.Load(brDecl, script); } else { return Internal.Load(brDecl, script); } } } } internal void Save(BinaryWriter bw, Script.SaveContext ctx) { using (var ms = new MemoryStream()) { using (var msbw = new BinaryWriter(ms, Encoding.UTF8, true)) { SaveCore(msbw, ctx); if (ms.Length > uint.MaxValue) throw new InvalidOperationException("Declaration length is greater than 4GB"); bw.Write((uint)ms.Length); bw.Write(ms); } } } internal abstract void SaveCore(BinaryWriter bw, Script.SaveContext ctx); protected void LoadArguments(BinaryReader br) { HasReturnArgument = br.ReadByte() != 0; Arguments = FunctionArgument.LoadForExternal(br); } protected void SaveArguments(BinaryWriter bw) { bw.Write(HasReturnArgument); foreach (var arg in Arguments) { bw.Write(arg.ArgumentType == FunctionArgumentType.Out); } } protected int SizeOfArguments => sizeof(byte) + (sizeof(byte) * Arguments.Count); } public abstract class BaseCC : Base { public NativeCallingConvention CallingConvention; public override string ToString() { switch (CallingConvention) { case NativeCallingConvention.Register: return "__fastcall"; case NativeCallingConvention.Pascal: return "__pascal"; case NativeCallingConvention.CDecl: return "__cdecl"; case NativeCallingConvention.Stdcall: return "__stdcall"; default: return ""; } } } public sealed class DLL : BaseCC { public string DllName; public string ProcedureName; public bool DelayLoad; public bool LoadWithAlteredSearchPath; internal override string Name => string.Format("{0}!{1}", DllName, ProcedureName); internal static new DLL Load(BinaryReader br, Script script) { var ret = new DLL(); ret.DllName = br.ReadAsciiStringTerminated(); ret.ProcedureName = br.ReadAsciiStringTerminated(); ret.CallingConvention = (NativeCallingConvention)br.ReadByte(); if (script.FileVersion >= Script.VERSION_MIN_DLL_LOAD_FLAGS) { ret.DelayLoad = br.ReadByte() != 0; ret.LoadWithAlteredSearchPath = br.ReadByte() != 0; } ret.LoadArguments(br); return ret; } public override string ToString() { return string.Format( "dll(\"{0}\",\"{1}\"{2}{3}) {4}", DllName.Replace("\"", "\\\""), ProcedureName.Replace("\"", "\\\""), DelayLoad ? ", delayload" : "", LoadWithAlteredSearchPath ? ", alteredsearchpath" : "", base.ToString() ); } public override int Size => DllString.Length + Encoding.ASCII.GetByteCount(DllName) + sizeof(byte) + Encoding.ASCII.GetByteCount(ProcedureName) + sizeof(byte) + sizeof(NativeCallingConvention) + sizeof(byte) + sizeof(byte) + SizeOfArguments; internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx) { bw.WriteAsciiString(DllString); bw.WriteAsciiStringTerminated(DllName); bw.WriteAsciiStringTerminated(ProcedureName); bw.Write(CallingConvention); if (ctx.FileVersion >= Script.VERSION_MIN_DLL_LOAD_FLAGS) { bw.Write((byte)(DelayLoad ? 1 : 0)); bw.Write((byte)(LoadWithAlteredSearchPath ? 1 : 0)); } SaveArguments(bw); } } public sealed class Class : BaseCC { public string ClassName; public string FunctionName; public bool IsProperty = false; internal override string Name => string.Format("{0}->{1}", ClassName, FunctionName); private const byte TERMINATOR = (byte)'|'; internal static new Class Load(BinaryReader br, Script script) { var ret = new Class(); // check for a special type if ((br.BaseStream.Length - br.BaseStream.Position) == 1) { ret.ClassName = "Class"; ret.HasReturnArgument = true; ret.CallingConvention = NativeCallingConvention.Pascal; var special = br.ReadByte(); switch (special) { case (byte)'+': ret.FunctionName = "CastToType"; ret.Arguments = new List() { new FunctionArgument() { Type = null, ArgumentType = FunctionArgumentType.Out }, new FunctionArgument() { Type = null, ArgumentType = FunctionArgumentType.Out } }; break; case (byte)'-': ret.FunctionName = "SetNil"; ret.Arguments = new List() { new FunctionArgument() { Type = null, ArgumentType = FunctionArgumentType.Out }, }; break; default: throw new InvalidDataException(string.Format("Unknown special type: 0x{0:x2}", special)); } return ret; } ret.ClassName = br.ReadAsciiStringTerminated(TERMINATOR); ret.FunctionName = br.ReadAsciiStringTerminated(TERMINATOR); ret.IsProperty = ret.FunctionName[ret.FunctionName.Length - 1] == '@'; if (ret.IsProperty) { ret.FunctionName = ret.FunctionName.Substring(0, ret.FunctionName.Length - 1); } ret.CallingConvention = (NativeCallingConvention)br.ReadByte(); ret.LoadArguments(br); return ret; } public override string ToString() { return string.Format( "class({0}, {1}{2}) {3}", ClassName, FunctionName, IsProperty ? ", property" : "", base.ToString() ); } private int SizeBody() { if (ClassName == "Class" && (FunctionName == "CastToType" || FunctionName == "SetNil")) return 1; return Encoding.ASCII.GetByteCount(ClassName) + sizeof(byte) + Encoding.ASCII.GetByteCount(FunctionName) + sizeof(byte) + (IsProperty ? sizeof(byte) : 0) + sizeof(NativeCallingConvention) + SizeOfArguments; } public override int Size => ClassString.Length + SizeBody(); internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx) { bw.WriteAsciiString(ClassString); if (ClassName == "Class") { if (FunctionName == "CastToType") { bw.Write((byte)'+'); return; } else if (FunctionName == "SetNil") { bw.Write((byte)'-'); return; } } bw.WriteAsciiStringTerminated(ClassName, TERMINATOR); var sb = new StringBuilder(FunctionName); if (IsProperty) sb.Append('@'); bw.WriteAsciiStringTerminated(sb.ToString(), TERMINATOR); bw.Write(CallingConvention); SaveArguments(bw); } } public sealed class COM : BaseCC { public uint VTableIndex; internal override string Name => string.Format("CoInterface->vtbl[{0}]", VTableIndex); internal static new COM Load(BinaryReader br, Script script) { var ret = new COM(); ret.VTableIndex = br.Read(); ret.CallingConvention = (NativeCallingConvention)br.ReadByte(); ret.LoadArguments(br); return ret; } public override string ToString() { return string.Format("com({0}) {1}", VTableIndex, base.ToString()); } public override int Size => ComString.Length + sizeof(uint) + sizeof(NativeCallingConvention) + SizeOfArguments; internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx) { bw.WriteAsciiString(ComString); bw.Write(VTableIndex); bw.Write(CallingConvention); SaveArguments(bw); } } public sealed class Internal : Base { internal static new Internal Load(BinaryReader br, Script script) { var ret = new Internal(); ret.LoadArguments(br); return ret; } public override string ToString() { return "internal"; } public override int Size => SizeOfArguments; internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx) { SaveArguments(bw); } } } /// /// A native function that imports from a DLL or an internal implementation /// public class ExternalFunction : FunctionBase { public FDecl.Base Declaration = null; private bool ExportsName => string.IsNullOrEmpty(Declaration?.Name); public override IType ReturnArgument { get; set; } /// /// If null, argument information is unknown. /// public override IList Arguments { get => Declaration?.Arguments; set { if (Declaration == null) throw new NullReferenceException("No declaration is present"); Declaration.Arguments = value; } } internal static ExternalFunction Load(BinaryReader br, Script script, bool exported) { var ret = new ExternalFunction(); ret.Exported = exported; var namelen = br.ReadByte(); if (namelen != 0) ret.Name = br.ReadAsciiString(namelen); if (exported) { ret.Declaration = FDecl.Base.Load(br, script); if (ret.Declaration.HasReturnArgument) ret.ReturnArgument = UnknownType.Instance; if (string.IsNullOrEmpty(ret.Name)) ret.Name = ret.Declaration.Name; } return ret; } public override string ToString() { var sb = new StringBuilder(".function"); if (Exported) sb.Append("(import)"); sb.Append(" external "); if (Declaration != null) sb.Append(Declaration); sb.Append(' '); sb.Append(ReturnArgument != null ? "returnsval " : "void "); sb.Append(Name); if (Declaration != null) { sb.Append('('); for (int i = 0; i < Arguments.Count; i++) { sb.Append(i == 0 ? "" : ","); sb.Append(Arguments[i].ToString()); } sb.Append(')'); } return sb.ToString(); } private byte NameLength() => (byte)Math.Min(0xff, Encoding.ASCII.GetByteCount(Name)); public override int Size => sizeof(byte) + NameLength() + (Declaration == null ? 0 : Declaration.Size); internal override void Save(BinaryWriter bw, Script.SaveContext ctx) { FunctionFlags flags = FunctionFlags.External; if (Exported) { flags |= FunctionFlags.Exported; Declaration.HasReturnArgument = ReturnArgument != null; } if (Attributes.Count != 0) flags |= FunctionFlags.HasAttributes; bw.Write(flags); if (ExportsName) { bw.Write(NameLength()); var internalName = Name.ToUpper(); if (Name.Length > 0xff) internalName = Name.Substring(0, 0xff); bw.WriteAsciiString(internalName); } else bw.Write(0); if (Declaration != null) { Declaration.Save(bw, ctx); } } } }