using System.Collections.Generic; using System.Linq; using System.Text; namespace SteamLanguageParser { class CSharpGen : ICodeGen { private static readonly Dictionary readerTypeMap = new() { {"byte", "Byte"}, {"short", "Int16"}, {"ushort", "UInt16"}, {"int", "Int32"}, {"uint", "UInt32"}, {"long", "Int64"}, {"ulong", "UInt64"}, {"char", "Char"}, }; public void EmitNamespace(StringBuilder sb, bool end, string nspace) { if (end) { sb.AppendLine("}"); sb.AppendLine("#pragma warning restore CS1591"); sb.AppendLine("#pragma warning restore CS0219"); sb.AppendLine("#pragma warning restore CA1041"); } else { sb.AppendLine("// "); sb.AppendLine("#pragma warning disable CS1591"); // this will hide "Missing XML comment for publicly visible type or member 'Type_or_Member'" sb.AppendLine("#pragma warning disable CS0219"); // Warning CS0219: The variable `(variable)' is assigned but its value is never used sb.AppendLine("#pragma warning disable CA1041"); // CA1041: Provide ObsoleteAttribute message sb.AppendLine("using System;"); sb.AppendLine("using System.IO;"); sb.AppendLine("using System.Runtime.InteropServices;"); sb.AppendLine("using System.Text;"); sb.AppendLine(); sb.AppendLine(string.Format("namespace {0}", nspace)); sb.AppendLine("{"); } } public void EmitSerialBase(StringBuilder sb, int level, bool supportsGC) { string padding = new string('\t', level); sb.AppendLine(padding + "public interface ISteamSerializable"); sb.AppendLine(padding + "{"); sb.AppendLine(padding + "\tvoid Serialize(Stream stream);"); sb.AppendLine(padding + "\tvoid Deserialize( Stream stream );"); sb.AppendLine(padding + "}"); sb.AppendLine(padding + "public interface ISteamSerializableHeader : ISteamSerializable"); sb.AppendLine(padding + "{"); sb.AppendLine(padding + "\tvoid SetEMsg( EMsg msg );"); sb.AppendLine(padding + "}"); sb.AppendLine(padding + "public interface ISteamSerializableMessage : ISteamSerializable"); sb.AppendLine(padding + "{"); sb.AppendLine(padding + "\tEMsg GetEMsg();"); sb.AppendLine(padding + "}"); if (supportsGC) { sb.AppendLine(padding + "public interface IGCSerializableHeader : ISteamSerializable"); sb.AppendLine(padding + "{"); sb.AppendLine(padding + "\tvoid SetEMsg( uint msg );"); sb.AppendLine(padding + "}"); sb.AppendLine(padding + "public interface IGCSerializableMessage : ISteamSerializable"); sb.AppendLine(padding + "{"); sb.AppendLine(padding + "\tuint GetEMsg();"); sb.AppendLine(padding + "}"); } sb.AppendLine(); } public static string EmitType(Symbol sym) { if (sym is WeakSymbol) { WeakSymbol wsym = sym as WeakSymbol; return wsym.Identifier; } else if (sym is StrongSymbol) { StrongSymbol ssym = sym as StrongSymbol; if (ssym.Prop == null) { return ssym.Class.Name; } else { return ssym.Class.Name + "." + ssym.Prop.Name; } } return "INVALID"; } public static string EmitMultipleTypes(List syms, string operation = "|") { var identList = syms.OfType().Select(wsym => wsym.Identifier); return string.Join(" " + operation + " ", identList); } public static string GetUpperName(string name) { return name[..1].ToUpperInvariant() + name.Remove(0, 1); } public void EmitNode(Node n, StringBuilder sb, int level) { if (n is ClassNode cn) { if (cn.Emit) { EmitClassNode(cn, sb, level); } } else if (n is EnumNode en) { EmitEnumNode(en, sb, level); } } private static void EmitEnumNode(EnumNode enode, StringBuilder sb, int level) { string padding = new string('\t', level); if (enode.Flags == "flags") { sb.AppendLine(padding + "[Flags]"); } if (enode.Type != null) { sb.AppendLine(padding + "public enum " + enode.Name + " : " + EmitType(enode.Type)); } else { sb.AppendLine(padding + "public enum " + enode.Name); } sb.AppendLine(padding + "{"); foreach (PropNode prop in enode.ChildNodes.Cast()) { if (!prop.Emit) { continue; } var lastValue = EmitMultipleTypes(prop.Default); if (prop.Obsolete != null) { if (prop.Obsolete.Length > 0) { sb.AppendLine(padding + "\t[Obsolete( \"" + prop.Obsolete + "\" )]"); } else { sb.AppendLine(padding + "\t[Obsolete]"); } } sb.AppendLine(padding + "\t" + prop.Name + " = " + lastValue + ","); } sb.AppendLine(padding + "}"); } private void EmitClassNode(ClassNode cnode, StringBuilder sb, int level) { EmitClassDef(cnode, sb, level, false); EmitClassIdentity(cnode, sb, level + 1); int baseSize = EmitClassProperties(cnode, sb, level + 1); EmitClassConstructor(cnode, sb, level + 1); EmitClassSerializer(cnode, sb, level + 1, baseSize); EmitClassDeserializer(cnode, sb, level + 1, baseSize); EmitClassDef(cnode, sb, level, true); } private static void EmitClassDef(ClassNode cnode, StringBuilder sb, int level, bool end) { string padding = new string('\t', level); if (end) { sb.AppendLine(padding + "}"); sb.AppendLine(); return; } string parent = "ISteamSerializable"; if (cnode.Ident != null) { if (cnode.Name.Contains("MsgGC")) { parent = "IGCSerializableMessage"; } else { parent = "ISteamSerializableMessage"; } } else if (cnode.Name.Contains("Hdr")) { if (cnode.Name.Contains("MsgGC")) { parent = "IGCSerializableHeader"; } else { parent = "ISteamSerializableHeader"; } } if (cnode.Name.Contains("Hdr")) { sb.AppendLine(padding + "[StructLayout( LayoutKind.Sequential )]"); } sb.AppendLine(padding + "public class " + cnode.Name + " : " + parent); sb.AppendLine(padding + "{"); } private static void EmitClassIdentity(ClassNode cnode, StringBuilder sb, int level) { string padding = new string('\t', level); if (cnode.Ident != null) { var supressObsoletionWarning = false; if (cnode.Ident is StrongSymbol strongSymbol) { if (strongSymbol.Prop is PropNode propNode && propNode.Obsolete != null) { supressObsoletionWarning = true; } } if (supressObsoletionWarning) { sb.AppendLine(padding + "#pragma warning disable 0612"); } if (cnode.Name.Contains("MsgGC")) { sb.AppendLine(padding + "public uint GetEMsg() { return " + EmitType(cnode.Ident) + "; }"); } else { sb.AppendLine(padding + "public EMsg GetEMsg() { return " + EmitType(cnode.Ident) + "; }"); } if (supressObsoletionWarning) { sb.AppendLine(padding + "#pragma warning restore 0612"); } sb.AppendLine(); } else if (cnode.Name.Contains("Hdr")) { if (cnode.Name.Contains("MsgGC")) { if (cnode.ChildNodes.Find(node => node.Name == "msg") != null) { sb.AppendLine(padding + "public void SetEMsg( uint msg ) { this.Msg = msg; }"); sb.AppendLine(); } else { // this is required for a gc header which doesn't have an emsg sb.AppendLine(padding + "public void SetEMsg( uint msg ) { }"); sb.AppendLine(); } } else { sb.AppendLine(padding + "public void SetEMsg( EMsg msg ) { this.Msg = msg; }"); sb.AppendLine(); } } } private static int EmitClassProperties(ClassNode cnode, StringBuilder sb, int level) { string padding = new string('\t', level); int baseClassSize = 0; if (cnode.Parent != null) { sb.AppendLine(padding + "public " + EmitType(cnode.Parent) + " Header { get; set; }"); } foreach (PropNode prop in cnode.ChildNodes.Cast()) { string typestr = EmitType(prop.Type); string propName = GetUpperName(prop.Name); if (prop.Flags != null && prop.Flags == "const") { sb.AppendLine(padding + "public static readonly " + typestr + " " + propName + " = " + EmitType(prop.Default.FirstOrDefault()) + ";"); continue; } int size = CodeGenerator.GetTypeSize(prop); baseClassSize += size; sb.AppendLine(padding + "// Static size: " + size); if (prop.Flags != null && prop.Flags == "steamidmarshal" && typestr == "ulong") { sb.AppendLine(padding + string.Format("private {0} {1};", typestr, prop.Name)); sb.AppendLine(padding + "public SteamID " + propName + " { get { return new SteamID( " + prop.Name + " ); } set { " + prop.Name + " = value.ConvertToUInt64(); } }"); } else if (prop.Flags != null && prop.Flags == "boolmarshal" && typestr == "byte") { sb.AppendLine(padding + string.Format("private {0} {1};", typestr, prop.Name)); sb.AppendLine(padding + "public bool " + propName + " { get { return ( " + prop.Name + " == 1 ); } set { " + prop.Name + " = ( byte )( value ? 1 : 0 ); } }"); } else if (prop.Flags != null && prop.Flags == "gameidmarshal" && typestr == "ulong") { sb.AppendLine(padding + string.Format("private {0} {1};", typestr, prop.Name)); sb.AppendLine(padding + "public GameID " + propName + " { get { return new GameID( " + prop.Name + " ); } set { " + prop.Name + " = value.ToUInt64(); } }"); } else { if (!string.IsNullOrEmpty(prop.FlagsOpt) && int.TryParse(prop.FlagsOpt, out _)) { typestr += "[]"; } sb.AppendLine(padding + "public " + typestr + " " + propName + " { get; set; }"); } } sb.AppendLine(); return baseClassSize; } private static void EmitClassConstructor(ClassNode cnode, StringBuilder sb, int level) { string padding = new string('\t', level); sb.AppendLine(padding + "public " + cnode.Name + "()"); sb.AppendLine(padding + "{"); if (cnode.Parent != null) { sb.AppendLine(padding + "\tHeader = new " + EmitType(cnode.Parent) + "();"); sb.AppendLine(padding + "\tHeader.Msg = GetEMsg();"); } foreach (PropNode prop in cnode.ChildNodes.Cast()) { Symbol defsym = prop.Default.FirstOrDefault(); string defflags = prop.Flags; string symname = GetUpperName(prop.Name); string ctor = EmitType(defsym); if (defflags != null && defflags == "proto") { ctor = "new " + EmitType(prop.Type) + "()"; } else if (defsym == null) { if (!string.IsNullOrEmpty(prop.FlagsOpt)) { ctor = "new " + EmitType(prop.Type) + "[" + CodeGenerator.GetTypeSize(prop) + "]"; } else { ctor = "0"; } } if (defflags != null && (defflags == "steamidmarshal" || defflags == "gameidmarshal" || defflags == "boolmarshal")) { symname = prop.Name; } else if (defflags != null && defflags == "const") { continue; } sb.AppendLine(padding + "\t" + symname + " = " + ctor + ";"); } sb.AppendLine(padding + "}"); } private void EmitClassSerializer(ClassNode cnode, StringBuilder sb, int level, int baseSize) { string padding = new string('\t', level); sb.AppendLine(); sb.AppendLine(padding + "public void Serialize(Stream stream)"); sb.AppendLine(padding + "{"); // first emit variable length members List varLengthProps = []; List openedStreams = []; varLengthProps.Add(baseSize.ToString()); if (cnode.Parent != null) { sb.AppendLine(padding + "\tHeader.Serialize(stream);"); varLengthProps.Add("(int)msHeader.Length"); openedStreams.Add("msHeader"); sb.AppendLine(); } foreach (PropNode prop in cnode.ChildNodes.Cast()) { string typestr = EmitType(prop.Type); int size = CodeGenerator.GetTypeSize(prop); if (size == 0) { if (prop.Flags != null && prop.Flags == "proto") { if (baseSize == 0) { // early exit sb.AppendLine(padding + "\tProtoBuf.Serializer.Serialize<" + typestr + ">(stream, " + GetUpperName(prop.Name) + ");"); sb.AppendLine(padding + "}"); return; } sb.AppendLine(padding + "\tMemoryStream ms" + GetUpperName(prop.Name) + " = new MemoryStream();"); sb.AppendLine(padding + "\tProtoBuf.Serializer.Serialize<" + typestr + ">(ms" + GetUpperName(prop.Name) + ", " + GetUpperName(prop.Name) + ");"); if (prop.FlagsOpt != null) { sb.AppendLine(padding + "\t" + GetUpperName(prop.FlagsOpt) + " = (int)ms" + GetUpperName(prop.Name) + ".Length;"); } //sb.AppendLine(padding + "\tms" + GetUpperName(prop.Name) + ".Seek( 0, SeekOrigin.Begin );"); } else { sb.AppendLine(padding + "\tMemoryStream ms" + GetUpperName(prop.Name) + " = " + GetUpperName(prop.Name) + ".serialize();"); } varLengthProps.Add("(int)ms" + GetUpperName(prop.Name) + ".Length"); openedStreams.Add("ms" + GetUpperName(prop.Name)); } } //sb.AppendLine(padding + "\tBinaryWriterEx bw = new BinaryWriterEx( stream );"); //sb.AppendLine(); sb.AppendLine(padding + "\tusing BinaryWriter bw = new BinaryWriter( stream, Encoding.UTF8, leaveOpen: true );"); sb.AppendLine(); if (cnode.Parent != null) { sb.AppendLine(padding + "\tmsHeader.CopyTo( msBuffer );"); } // next emit writers foreach (PropNode prop in cnode.ChildNodes.Cast()) { string typecast = ""; string propName = GetUpperName(prop.Name); if (prop.Type is StrongSymbol strongSymbol && strongSymbol.Class is EnumNode enode) { if (enode.Type is WeakSymbol weakSymbol) { typecast = "(" + weakSymbol.Identifier + ")"; } else { typecast = "(int)"; } } if (prop.Flags != null) { if (prop.Flags == "steamidmarshal" || prop.Flags == "gameidmarshal" || prop.Flags == "boolmarshal") { propName = prop.Name; } else if (prop.Flags == "proto") { sb.AppendLine(padding + "\tbw.Write( ms" + propName + ".ToArray() );"); continue; } else if (prop.Flags == "const") { continue; } } if (prop.Flags == "protomask") { propName = "MsgUtil.MakeMsg( " + propName + ", true )"; } else if (prop.Flags == "protomaskgc") { propName = "MsgUtil.MakeGCMsg( " + propName + ", true )"; } sb.AppendLine(padding + "\tbw.Write( " + typecast + propName + " );"); } sb.AppendLine(); foreach (string stream in openedStreams) { sb.AppendLine(padding + "\t" + stream + ".Dispose();"); } //sb.AppendLine(); //sb.AppendLine(padding + "\tmsBuffer.Seek( 0, SeekOrigin.Begin );"); sb.AppendLine(padding + "}"); } private void EmitClassDeserializer(ClassNode cnode, StringBuilder sb, int level, int baseSize) { string padding = new string('\t', level); sb.AppendLine(); sb.AppendLine(padding + "public void Deserialize( Stream stream )"); sb.AppendLine(padding + "{"); if (baseSize > 0) { sb.AppendLine(padding + "\tusing BinaryReader br = new BinaryReader( stream, Encoding.UTF8, leaveOpen: true );"); sb.AppendLine(); } if (cnode.Parent != null) { sb.AppendLine(padding + "\tHeader.Deserialize( stream );"); } foreach (PropNode prop in cnode.ChildNodes.Cast()) { string typestr = EmitType(prop.Type); int size = CodeGenerator.GetTypeSize(prop); string defflags = prop.Flags; string symname = GetUpperName(prop.Name); if (defflags != null && (defflags == "steamidmarshal" || defflags == "gameidmarshal" || defflags == "boolmarshal")) { symname = prop.Name; } else if (defflags != null && defflags == "const") { continue; } if (size == 0) { if (prop.Flags != null && prop.Flags == "proto") { if (prop.FlagsOpt != null) { sb.AppendLine(padding + "\tusing( MemoryStream ms" + GetUpperName(prop.Name) + " = new MemoryStream( br.ReadBytes( " + GetUpperName(prop.FlagsOpt) + " ) ) )"); sb.AppendLine(padding + "\t\t" + GetUpperName(prop.Name) + " = ProtoBuf.Serializer.Deserialize<" + typestr + ">( ms" + GetUpperName(prop.Name) + " );"); } else { sb.AppendLine(padding + "\t" + GetUpperName(prop.Name) + " = ProtoBuf.Serializer.Deserialize<" + typestr + ">( stream );"); } } else { sb.AppendLine(padding + "\t" + GetUpperName(prop.Name) + ".Deserialize( stream );"); } } else { string typecast = ""; if (!readerTypeMap.ContainsKey(typestr)) { typecast = "(" + typestr + ")"; typestr = CodeGenerator.GetTypeOfSize(size, SupportsUnsignedTypes()); } string call = "br.Read" + readerTypeMap[typestr] + "()"; if (!string.IsNullOrEmpty(prop.FlagsOpt)) { call = "br.Read" + readerTypeMap[typestr] + "s( " + prop.FlagsOpt + " )"; } if (prop.Flags == "protomask") { call = "MsgUtil.GetMsg( (uint)" + call + " )"; } else if (prop.Flags == "protomaskgc") { call = "MsgUtil.GetGCMsg( (uint)" + call + " )"; } sb.AppendLine(padding + "\t" + symname + " = " + typecast + call + ";"); } } sb.AppendLine(padding + "}"); } public bool SupportsUnsignedTypes() { return true; } public bool SupportsNamespace() { return true; } } }