using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Generator { public partial class CodeWriter { public class TypeDef { public string Name; public string NativeType; public string ManagedType; } private Dictionary TypeDefs = new Dictionary(); // // Don't give a fuck about these classes, they just cause us trouble // public readonly static string[] SkipStructs = new string[] { "CSteamID", "CSteamAPIContext", "CCallResult", "CCallback", "ValvePackingSentinel_t", "CCallbackBase", "CSteamGameServerAPIContext" }; public readonly static string[] ForceLargePackStructs = new string[] { "LeaderboardEntry_t" }; void Structs() { var callbackList = new List(); foreach ( var c in def.structs ) { var name = Cleanup.ConvertType( c.Name ); if ( SkipStructs.Contains( c.Name ) ) continue; if ( !Cleanup.ShouldCreate( name ) ) continue; if ( name.Contains( "::" ) ) continue; int defaultPack = 8; if ( c.Fields.Any( x => x.Type.Contains( "CSteamID" ) ) && !ForceLargePackStructs.Contains( c.Name ) ) defaultPack = 4; var isCallback = !string.IsNullOrEmpty( c.CallbackId ); // // Main struct // WriteLine( "[StructLayout( LayoutKind.Sequential, Pack = 4 )]" ); StartBlock( $"{Cleanup.Expose( name )} struct {name}" ); { // // The fields // StructFields( c.Fields ); WriteLine(); if ( isCallback ) { WriteLine( "#region SteamCallback" ); { if ( defaultPack == 4 ) { WriteLine( $"internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof({name}) );" ); WriteLine( $"internal static {name} Fill( IntPtr p ) => (({name})({name}) Marshal.PtrToStructure( p, typeof({name}) ) );" ); } else { WriteLine( $"internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( Config.PackSmall ? typeof({name}) : typeof(Pack8) );" ); WriteLine( $"internal static {name} Fill( IntPtr p ) => Config.PackSmall ? (({name})({name}) Marshal.PtrToStructure( p, typeof({name}) )) : (({name})(Pack8) Marshal.PtrToStructure( p, typeof(Pack8) ));" ); } WriteLine(); WriteLine( $"static Action<{name}> actionClient;" ); WriteLine( $"[MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) );" ); WriteLine( $"static Action<{name}> actionServer;" ); WriteLine( $"[MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) );" ); StartBlock( $"public static void Install( Action<{name}> action, bool server = false )" ); { StartBlock( "if ( server )" ); { WriteLine( $"Event.Register( OnServer, StructSize, {c.CallbackId}, true );" ); WriteLine( $"actionServer = action;" ); } Else(); { WriteLine( $"Event.Register( OnClient, StructSize, {c.CallbackId}, false );" ); WriteLine( $"actionClient = action;" ); } EndBlock(); } EndBlock(); StartBlock( $"public static async Task<{name}?> GetResultAsync( SteamAPICall_t handle )" ); { WriteLine( $"bool failed = false;" ); WriteLine(); StartBlock( $"while ( !SteamUtils.IsCallComplete( handle, out failed ) )" ); { WriteLine( $"await Task.Delay( 1 );" ); } EndBlock(); WriteLine( $"if ( failed ) return null;" ); WriteLine( $"" ); WriteLine( $"var ptr = Marshal.AllocHGlobal( StructSize );" ); WriteLine( $"" ); WriteLine( $"try" ); WriteLine( $"{{" ); WriteLine( $" if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, {c.CallbackId}, ref failed ) || failed )" ); WriteLine( $" return null;" ); WriteLine( $"" ); WriteLine( $" return Fill( ptr );" ); WriteLine( $"}}" ); WriteLine( $"finally" ); WriteLine( $"{{" ); WriteLine( $" Marshal.FreeHGlobal( ptr );" ); WriteLine( $"}}" ); } EndBlock(); } WriteLine( "#endregion" ); } else { WriteLine( "#region Marshalling" ); { if ( defaultPack == 4 ) { //WriteLine( $"internal static int GetStructSize() => System.Runtime.InteropServices.Marshal.SizeOf( typeof({name}) );" ); WriteLine( $"internal static {name} Fill( IntPtr p ) => (({name})({name}) Marshal.PtrToStructure( p, typeof({name}) ) );" ); } else { //WriteLine( $"internal static int GetStructSize() => System.Runtime.InteropServices.Marshal.SizeOf( Config.PackSmall ? typeof({name}) : typeof(Pack8) );" ); WriteLine( $"internal static {name} Fill( IntPtr p ) => Config.PackSmall ? (({name})({name}) Marshal.PtrToStructure( p, typeof({name}) )) : (({name})(Pack8) Marshal.PtrToStructure( p, typeof(Pack8) ));" ); } } WriteLine( "#endregion" ); } if ( defaultPack != 4 ) { WriteLine( "#region Packed Versions" ); { // // Windows Packed version // WriteLine(); WriteLine( $"[StructLayout( LayoutKind.Sequential, Pack = {defaultPack} )]" ); StartBlock( $"public struct Pack8" ); { StructFields( c.Fields ); // // Implicit convert from PackSmall to regular // WriteLine(); Write( $"public static implicit operator {name} ( {name}.Pack8 d ) => " ); { Write( $"new {name}{{ " ); { foreach ( var f in c.Fields ) { Write( $"{CleanMemberName( f.Name )} = d.{CleanMemberName( f.Name )}," ); } } WriteLine( " };" ); } } EndBlock(); } WriteLine( "#endregion" ); } if ( !string.IsNullOrEmpty( c.CallbackId ) ) { callbackList.Add( c ); } } EndBlock(); WriteLine(); } } private void StructFields( SteamApiDefinition.StructDef.StructFields[] fields ) { foreach ( var m in fields ) { var t = ToManagedType( m.Type ); t = Cleanup.ConvertType( t ); if ( TypeDefs.ContainsKey( t ) ) { t = TypeDefs[t].ManagedType; } if ( t == "bool" ) { WriteLine( "[MarshalAs(UnmanagedType.I1)]" ); } if ( t.StartsWith( "char " ) && t.Contains( "[" ) ) { var num = t.Replace( "char", "" ).Trim( '[', ']', ' ' ); t = "string"; WriteLine( $"[MarshalAs(UnmanagedType.ByValTStr, SizeConst = {num})]" ); } if ( t.StartsWith( "uint8 " ) && t.Contains( "[" ) ) { var num = t.Replace( "uint8", "" ).Trim( '[', ']', ' ' ); t = "byte[]"; WriteLine( $"[MarshalAs(UnmanagedType.ByValArray, SizeConst = {num})] // {m.Name}" ); } if ( t.StartsWith( "SteamId" ) && t.Contains( "[" ) ) { var num = t.Replace( "SteamId", "" ).Trim( '[', ']', ' ' ); t = $"ulong[]"; WriteLine( $"[MarshalAs(UnmanagedType.ByValArray, SizeConst = {num}, ArraySubType = UnmanagedType.U8)]" ); } if ( t.StartsWith( "PublishedFileId " ) && t.Contains( "[" ) ) { var num = t.Replace( "PublishedFileId", "" ).Trim( '[', ']', ' ' ); t = $"PublishedFileId[]"; WriteLine( $"[MarshalAs(UnmanagedType.ByValArray, SizeConst = {num}, ArraySubType = UnmanagedType.U8)]" ); } if ( t.StartsWith( "uint32 " ) && t.Contains( "[" ) ) { var num = t.Replace( "uint32", "" ).Trim( '[', ']', ' ' ); t = $"uint[]"; WriteLine( $"[MarshalAs(UnmanagedType.ByValArray, SizeConst = {num}, ArraySubType = UnmanagedType.U4)]" ); } if ( t.StartsWith( "float " ) && t.Contains( "[" ) ) { var num = t.Replace( "float", "" ).Trim( '[', ']', ' ' ); t = $"float[]"; WriteLine( $"[MarshalAs(UnmanagedType.ByValArray, SizeConst = {num}, ArraySubType = UnmanagedType.R4)]" ); } if ( t == "const char **" ) { t = "IntPtr"; } if (t.StartsWith("AppId ") && t.Contains("[")) { var num = t.Replace("AppId", "").Trim('[', ']', ' '); t = $"AppId[]"; WriteLine($"[MarshalAs(UnmanagedType.ByValArray, SizeConst = {num}, ArraySubType = UnmanagedType.U4)]"); } WriteLine( $"internal {t} {CleanMemberName( m.Name )}; // {m.Name} {m.Type}" ); } } } }