Restructuring of the files

This commit is contained in:
2025-02-25 12:58:41 +02:00
parent 8a25503432
commit b37fd411c6
154 changed files with 5 additions and 12 deletions

View File

@@ -0,0 +1,24 @@
using System;
namespace Facepunch.Steamworks
{
static internal class Epoch
{
private static readonly DateTime epoch = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc );
/// <summary>
/// Returns the current Unix Epoch
/// </summary>
public static int Current => (int)(DateTime.UtcNow.Subtract( epoch ).TotalSeconds);
/// <summary>
/// Convert an epoch to a datetime
/// </summary>
public static DateTime ToDateTime( decimal unixTime ) => epoch.AddSeconds( (long)unixTime );
/// <summary>
/// Convert a DateTime to a unix time
/// </summary>
public static uint FromDateTime( DateTime dt ) => (uint)(dt.Subtract( epoch ).TotalSeconds);
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;
namespace Facepunch.Steamworks
{
internal static class Helpers
{
public const int MemoryBufferSize = 1024 * 32;
[ThreadStatic] private static IntPtr[] MemoryPool;
[ThreadStatic] private static int MemoryPoolIndex;
public static unsafe IntPtr TakeMemory()
{
if ( MemoryPool == null )
{
//
// The pool has 4 items. This should be safe because we shouldn't really
// ever be using more than 2 memory pools
//
MemoryPool = new IntPtr[4];
for ( int i = 0; i < MemoryPool.Length; i++ )
MemoryPool[i] = Marshal.AllocHGlobal( MemoryBufferSize );
}
MemoryPoolIndex++;
if ( MemoryPoolIndex >= MemoryPool.Length )
MemoryPoolIndex = 0;
var take = MemoryPool[MemoryPoolIndex];
((byte*)take)[0] = 0;
return take;
}
[ThreadStatic] private static byte[][] BufferPool;
[ThreadStatic] private static int BufferPoolIndex;
/// <summary>
/// Returns a buffer. This will get returned and reused later on.
/// We shouldn't really be using this anymore.
/// </summary>
public static byte[] TakeBuffer( int minSize )
{
if ( BufferPool == null )
{
//
// The pool has 4 items.
//
BufferPool = new byte[4][];
for ( int i = 0; i < BufferPool.Length; i++ )
BufferPool[i] = new byte[ 1024 * 128 ];
}
BufferPoolIndex++;
if ( BufferPoolIndex >= BufferPool.Length )
BufferPoolIndex = 0;
if ( BufferPool[BufferPoolIndex].Length < minSize )
{
BufferPool[BufferPoolIndex] = new byte[minSize + 1024];
}
return BufferPool[BufferPoolIndex];
}
internal unsafe static string MemoryToString( IntPtr ptr )
{
var len = 0;
for( len = 0; len < MemoryBufferSize; len++ )
{
if ( ((byte*)ptr)[len] == 0 )
break;
}
if ( len == 0 )
return string.Empty;
return UTF8Encoding.UTF8.GetString( (byte*)ptr, len );
}
}
internal class MonoPInvokeCallbackAttribute : Attribute
{
public MonoPInvokeCallbackAttribute() { }
}
/// <summary>
/// Prevent unity from stripping shit we depend on
/// https://docs.unity3d.com/Manual/ManagedCodeStripping.html
/// </summary>
internal class PreserveAttribute : System.Attribute { }
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
namespace Facepunch.Steamworks
{
internal static class Platform
{
/*#if PLATFORM_WIN32
public const int StructPlatformPackSize = 8;
public const string LibraryName = "steam_api";
#elif PLATFORM_POSIX
public const int StructPlatformPackSize = 4;
public const string LibraryName = "libsteam_api";
#else*/
public const int StructPlatformPackSize = 8;
public const string LibraryName = "steam_api64";
//#endif
public const CallingConvention CC = CallingConvention.Cdecl;
public const int StructPackSize = 4;
}
}

View File

@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Facepunch.Steamworks.Data;
namespace Facepunch.Steamworks
{
internal static class SourceServerQuery
{
private static readonly byte[] A2S_SERVERQUERY_GETCHALLENGE = { 0x55, 0xFF, 0xFF, 0xFF, 0xFF };
// private static readonly byte A2S_PLAYER = 0x55;
private const byte A2S_RULES = 0x56;
private static readonly Dictionary<IPEndPoint, Task<Dictionary<string, string>>> PendingQueries =
new Dictionary<IPEndPoint, Task<Dictionary<string, string>>>();
internal static Task<Dictionary<string, string>> GetRules( ServerInfo server )
{
var endpoint = new IPEndPoint(server.Address, server.QueryPort);
lock (PendingQueries)
{
if (PendingQueries.TryGetValue(endpoint, out var pending))
return pending;
var task = GetRulesImpl( endpoint )
.ContinueWith(t =>
{
lock (PendingQueries)
{
PendingQueries.Remove(endpoint);
}
return t;
})
.Unwrap();
PendingQueries.Add(endpoint, task);
return task;
}
}
private static async Task<Dictionary<string, string>> GetRulesImpl( IPEndPoint endpoint )
{
try
{
using (var client = new UdpClient())
{
client.Client.SendTimeout = 3000;
client.Client.ReceiveTimeout = 3000;
client.Connect(endpoint);
return await GetRules(client);
}
}
catch (System.Exception)
{
//Console.Error.WriteLine( e.Message );
return null;
}
}
static async Task<Dictionary<string, string>> GetRules( UdpClient client )
{
var challengeBytes = await GetChallengeData( client );
challengeBytes[0] = A2S_RULES;
await Send( client, challengeBytes );
var ruleData = await Receive( client );
var rules = new Dictionary<string, string>();
using ( var br = new BinaryReader( new MemoryStream( ruleData ) ) )
{
if ( br.ReadByte() != 0x45 )
throw new Exception( "Invalid data received in response to A2S_RULES request" );
var numRules = br.ReadUInt16();
for ( int index = 0; index < numRules; index++ )
{
rules.Add( br.ReadNullTerminatedUTF8String(), br.ReadNullTerminatedUTF8String() );
}
}
return rules;
}
static async Task<byte[]> Receive( UdpClient client )
{
byte[][] packets = null;
byte packetNumber = 0, packetCount = 1;
do
{
var result = await client.ReceiveAsync();
var buffer = result.Buffer;
using ( var br = new BinaryReader( new MemoryStream( buffer ) ) )
{
var header = br.ReadInt32();
if ( header == -1 )
{
var unsplitdata = new byte[buffer.Length - br.BaseStream.Position];
Buffer.BlockCopy( buffer, (int)br.BaseStream.Position, unsplitdata, 0, unsplitdata.Length );
return unsplitdata;
}
else if ( header == -2 )
{
int requestId = br.ReadInt32();
packetNumber = br.ReadByte();
packetCount = br.ReadByte();
int splitSize = br.ReadInt32();
}
else
{
throw new System.Exception( "Invalid Header" );
}
if ( packets == null ) packets = new byte[packetCount][];
var data = new byte[buffer.Length - br.BaseStream.Position];
Buffer.BlockCopy( buffer, (int)br.BaseStream.Position, data, 0, data.Length );
packets[packetNumber] = data;
}
}
while ( packets.Any( p => p == null ) );
var combinedData = Combine( packets );
return combinedData;
}
private static async Task<byte[]> GetChallengeData( UdpClient client )
{
await Send( client, A2S_SERVERQUERY_GETCHALLENGE );
var challengeData = await Receive( client );
if ( challengeData[0] != 0x41 )
throw new Exception( "Invalid Challenge" );
return challengeData;
}
static async Task Send( UdpClient client, byte[] message )
{
var sendBuffer = new byte[message.Length + 4];
sendBuffer[0] = 0xFF;
sendBuffer[1] = 0xFF;
sendBuffer[2] = 0xFF;
sendBuffer[3] = 0xFF;
Buffer.BlockCopy( message, 0, sendBuffer, 4, message.Length );
await client.SendAsync( sendBuffer, message.Length + 4 );
}
static byte[] Combine( byte[][] arrays )
{
var rv = new byte[arrays.Sum( a => a.Length )];
int offset = 0;
foreach ( byte[] array in arrays )
{
Buffer.BlockCopy( array, 0, rv, offset, array.Length );
offset += array.Length;
}
return rv;
}
};
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
namespace Facepunch.Steamworks
{
internal abstract class SteamInterface
{
public virtual IntPtr GetUserInterfacePointer() => IntPtr.Zero;
public virtual IntPtr GetServerInterfacePointer() => IntPtr.Zero;
public virtual IntPtr GetGlobalInterfacePointer() => IntPtr.Zero;
public IntPtr Self;
public IntPtr SelfGlobal;
public IntPtr SelfServer;
public IntPtr SelfClient;
public bool IsValid => Self != IntPtr.Zero;
public bool IsServer { get; private set; }
internal void SetupInterface( bool gameServer )
{
if ( Self != IntPtr.Zero )
return;
IsServer = gameServer;
SelfGlobal = GetGlobalInterfacePointer();
Self = SelfGlobal;
if ( Self != IntPtr.Zero )
return;
if ( gameServer )
{
SelfServer = GetServerInterfacePointer();
Self = SelfServer;
}
else
{
SelfClient = GetUserInterfacePointer();
Self = SelfClient;
}
}
internal void ShutdownInterface()
{
Self = IntPtr.Zero;
}
}
public abstract class SteamClass
{
internal abstract void InitializeInterface( bool server );
internal abstract void DestroyInterface( bool server );
}
public class SteamSharedClass<T> : SteamClass
{
internal static SteamInterface Interface => InterfaceClient ?? InterfaceServer;
internal static SteamInterface InterfaceClient;
internal static SteamInterface InterfaceServer;
internal override void InitializeInterface( bool server )
{
}
internal virtual void SetInterface( bool server, SteamInterface iface )
{
if ( server )
{
InterfaceServer = iface;
}
if ( !server )
{
InterfaceClient = iface;
}
}
internal override void DestroyInterface( bool server )
{
if ( !server )
{
InterfaceClient = null;
}
if ( server )
{
InterfaceServer = null;
}
}
}
public class SteamClientClass<T> : SteamClass
{
internal static SteamInterface Interface;
internal override void InitializeInterface( bool server )
{
}
internal virtual void SetInterface( bool server, SteamInterface iface )
{
if ( server )
throw new System.NotSupportedException();
Interface = iface;
}
internal override void DestroyInterface( bool server )
{
Interface = null;
}
}
public class SteamServerClass<T> : SteamClass
{
internal static SteamInterface Interface;
internal override void InitializeInterface( bool server )
{
}
internal virtual void SetInterface( bool server, SteamInterface iface )
{
if ( !server )
throw new System.NotSupportedException();
Interface = iface;
}
internal override void DestroyInterface( bool server )
{
Interface = null;
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
namespace Facepunch.Steamworks
{
internal unsafe class Utf8StringToNative : ICustomMarshaler
{
public IntPtr MarshalManagedToNative(object managedObj)
{
if ( managedObj == null )
return IntPtr.Zero;
if ( managedObj is string str )
{
fixed ( char* strPtr = str )
{
int len = Encoding.UTF8.GetByteCount( str );
var mem = Marshal.AllocHGlobal( len + 1 );
var wlen = System.Text.Encoding.UTF8.GetBytes( strPtr, str.Length, (byte*)mem, len + 1 );
( (byte*)mem )[wlen] = 0;
return mem;
}
}
return IntPtr.Zero;
}
public object MarshalNativeToManaged(IntPtr pNativeData) => throw new System.NotImplementedException();
public void CleanUpNativeData(IntPtr pNativeData) => Marshal.FreeHGlobal( pNativeData );
public void CleanUpManagedData(object managedObj) => throw new System.NotImplementedException();
public int GetNativeDataSize() => -1;
[Preserve]
public static ICustomMarshaler GetInstance(string cookie) => new Utf8StringToNative();
}
internal struct Utf8StringPointer
{
#pragma warning disable 649
internal IntPtr ptr;
#pragma warning restore 649
public unsafe static implicit operator string( Utf8StringPointer p )
{
if ( p.ptr == IntPtr.Zero )
return null;
var bytes = (byte*)p.ptr;
var dataLen = 0;
while ( dataLen < 1024 * 1024 * 64 )
{
if ( bytes[dataLen] == 0 )
break;
dataLen++;
}
return Encoding.UTF8.GetString( bytes, dataLen );
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
namespace Facepunch.Steamworks
{
public static partial class Utility
{
static internal T ToType<T>( this IntPtr ptr )
{
if ( ptr == IntPtr.Zero )
return default;
return (T)Marshal.PtrToStructure( ptr, typeof( T ) );
}
static internal object ToType( this IntPtr ptr, System.Type t )
{
if ( ptr == IntPtr.Zero )
return default;
return Marshal.PtrToStructure( ptr, t );
}
static internal uint Swap( uint x )
{
return ((x & 0x000000ff) << 24) +
((x & 0x0000ff00) << 8) +
((x & 0x00ff0000) >> 8) +
((x & 0xff000000) >> 24);
}
static public uint IpToInt32( this IPAddress ipAddress )
{
return Swap( (uint) ipAddress.Address );
}
static public IPAddress Int32ToIp( uint ipAddress )
{
return new IPAddress( Swap( ipAddress ) );
}
public static string FormatPrice(string currency, double price)
{
var decimaled = price.ToString("0.00");
switch (currency)
{
case "AED": return $"{decimaled}د.إ";
case "ARS": return $"${decimaled} ARS";
case "AUD": return $"A${decimaled}";
case "BRL": return $"R${decimaled}";
case "CAD": return $"C${decimaled}";
case "CHF": return $"Fr. {decimaled}";
case "CLP": return $"${decimaled} CLP";
case "CNY": return $"{decimaled}元";
case "COP": return $"COL$ {decimaled}";
case "CRC": return $"₡{decimaled}";
case "EUR": return $"€{decimaled}";
case "SEK": return $"{decimaled}kr";
case "GBP": return $"£{decimaled}";
case "HKD": return $"HK${decimaled}";
case "ILS": return $"₪{decimaled}";
case "IDR": return $"Rp{decimaled}";
case "INR": return $"₹{decimaled}";
case "JPY": return $"¥{decimaled}";
case "KRW": return $"₩{decimaled}";
case "KWD": return $"KD {decimaled}";
case "KZT": return $"{decimaled}₸";
case "MXN": return $"Mex${decimaled}";
case "MYR": return $"RM {decimaled}";
case "NOK": return $"{decimaled} kr";
case "NZD": return $"${decimaled} NZD";
case "PEN": return $"S/. {decimaled}";
case "PHP": return $"₱{decimaled}";
case "PLN": return $"{decimaled}zł";
case "QAR": return $"QR {decimaled}";
case "RUB": return $"{decimaled}₽";
case "SAR": return $"SR {decimaled}";
case "SGD": return $"S${decimaled}";
case "THB": return $"฿{decimaled}";
case "TRY": return $"₺{decimaled}";
case "TWD": return $"NT$ {decimaled}";
case "UAH": return $"₴{decimaled}";
case "USD": return $"${decimaled}";
case "UYU": return $"$U {decimaled}"; // yes the U goes after $
case "VND": return $"₫{decimaled}";
case "ZAR": return $"R {decimaled}";
// TODO - check all of them https://partner.steamgames.com/doc/store/pricing/currencies
default: return $"{decimaled} {currency}";
}
}
static readonly byte[] readBuffer = new byte[1024 * 8];
public static string ReadNullTerminatedUTF8String( this BinaryReader br )
{
lock ( readBuffer )
{
byte chr;
int i = 0;
while ( (chr = br.ReadByte()) != 0 && i < readBuffer.Length )
{
readBuffer[i] = chr;
i++;
}
return Encoding.UTF8.GetString( readBuffer, 0, i );
}
}
}
}