(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal static class Helpers
|
||||
{
|
||||
public const int MaxStringSize = 1024 * 32;
|
||||
|
||||
private static IntPtr[] MemoryPool;
|
||||
private static int MemoryPoolIndex;
|
||||
|
||||
public static unsafe IntPtr TakeMemory()
|
||||
{
|
||||
if ( MemoryPool == null )
|
||||
{
|
||||
//
|
||||
// The pool has 5 items. This should be safe because we shouldn't really
|
||||
// ever be using more than 2 memory pools
|
||||
//
|
||||
MemoryPool = new IntPtr[5];
|
||||
|
||||
for ( int i = 0; i < MemoryPool.Length; i++ )
|
||||
MemoryPool[i] = Marshal.AllocHGlobal( MaxStringSize );
|
||||
}
|
||||
|
||||
MemoryPoolIndex++;
|
||||
if ( MemoryPoolIndex >= MemoryPool.Length )
|
||||
MemoryPoolIndex = 0;
|
||||
|
||||
var take = MemoryPool[MemoryPoolIndex];
|
||||
|
||||
((byte*)take)[0] = 0;
|
||||
|
||||
return take;
|
||||
}
|
||||
|
||||
|
||||
private static byte[][] BufferPool;
|
||||
private static int BufferPoolIndex;
|
||||
private static object BufferMutex = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a buffer. This will get returned and reused later on.
|
||||
/// </summary>
|
||||
public static byte[] TakeBuffer( int minSize )
|
||||
{
|
||||
int bufferPoolIndex;
|
||||
lock (BufferMutex)
|
||||
{
|
||||
if (BufferPool == null)
|
||||
{
|
||||
//
|
||||
// The pool has 8 items.
|
||||
//
|
||||
BufferPool = new byte[8][];
|
||||
|
||||
for (int i = 0; i < BufferPool.Length; i++)
|
||||
BufferPool[i] = new byte[1024 * 128];
|
||||
}
|
||||
|
||||
BufferPoolIndex++;
|
||||
|
||||
if (BufferPoolIndex < 0 || BufferPoolIndex >= BufferPool.Length)
|
||||
BufferPoolIndex = 0;
|
||||
|
||||
bufferPoolIndex = BufferPoolIndex;
|
||||
}
|
||||
|
||||
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 < MaxStringSize; 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 { }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
internal static class Platform
|
||||
{
|
||||
#if PLATFORM_WIN64
|
||||
public const int StructPlatformPackSize = 8;
|
||||
public const string LibraryName = "steam_api64";
|
||||
public const CallingConvention MemberConvention = CallingConvention.Cdecl;
|
||||
#elif PLATFORM_WIN32
|
||||
public const int StructPlatformPackSize = 8;
|
||||
public const string LibraryName = "steam_api";
|
||||
public const CallingConvention MemberConvention = CallingConvention.ThisCall;
|
||||
#elif PLATFORM_POSIX32
|
||||
public const int StructPlatformPackSize = 4;
|
||||
public const string LibraryName = "libsteam_api";
|
||||
public const CallingConvention MemberConvention = CallingConvention.Cdecl;
|
||||
#elif PLATFORM_POSIX64
|
||||
public const int StructPlatformPackSize = 4;
|
||||
public const string LibraryName = "libsteam_api64";
|
||||
public const CallingConvention MemberConvention = CallingConvention.Cdecl;
|
||||
#endif
|
||||
|
||||
public const int StructPackSize = 4;
|
||||
|
||||
|
||||
|
||||
public static int MemoryOffset( int memLocation )
|
||||
{
|
||||
#if PLATFORM_64
|
||||
return memLocation;
|
||||
#else
|
||||
return memLocation / 2;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,203 +4,147 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
namespace 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 static readonly byte A2S_RULES = 0x56;
|
||||
|
||||
internal class SourceServerQuery : IDisposable
|
||||
{
|
||||
public static List<SourceServerQuery> Current = new List<SourceServerQuery>();
|
||||
internal static async Task<Dictionary<string, string>> GetRules( ServerInfo server )
|
||||
{
|
||||
try
|
||||
{
|
||||
var endpoint = new IPEndPoint( server.Address, server.QueryPort );
|
||||
|
||||
public static void Cycle()
|
||||
{
|
||||
if ( Current.Count == 0 )
|
||||
return;
|
||||
using ( var client = new UdpClient() )
|
||||
{
|
||||
client.Client.SendTimeout = 3000;
|
||||
client.Client.ReceiveTimeout = 3000;
|
||||
client.Connect( endpoint );
|
||||
|
||||
for( int i = Current.Count; i>0; i-- )
|
||||
{
|
||||
Current[i-1].Update();
|
||||
}
|
||||
}
|
||||
return await GetRules( client );
|
||||
}
|
||||
}
|
||||
catch ( System.Exception e )
|
||||
{
|
||||
Console.Error.WriteLine( e.Message );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly byte[] A2S_SERVERQUERY_GETCHALLENGE = { 0x55, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
// private static readonly byte A2S_PLAYER = 0x55;
|
||||
private static readonly byte A2S_RULES = 0x56;
|
||||
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 );
|
||||
|
||||
public volatile bool IsRunning;
|
||||
public volatile bool IsSuccessful;
|
||||
var rules = new Dictionary<string, string>();
|
||||
|
||||
private ServerList.Server Server;
|
||||
private UdpClient udpClient;
|
||||
private IPEndPoint endPoint;
|
||||
private System.Threading.Thread thread;
|
||||
private byte[] _challengeBytes;
|
||||
using ( var br = new BinaryReader( new MemoryStream( ruleData ) ) )
|
||||
{
|
||||
if ( br.ReadByte() != 0x45 )
|
||||
throw new Exception( "Invalid data received in response to A2S_RULES request" );
|
||||
|
||||
private Dictionary<string, string> rules = new Dictionary<string, string>();
|
||||
var numRules = br.ReadUInt16();
|
||||
for ( int index = 0; index < numRules; index++ )
|
||||
{
|
||||
rules.Add( br.ReadNullTerminatedUTF8String( readBuffer ), br.ReadNullTerminatedUTF8String( readBuffer ) );
|
||||
}
|
||||
}
|
||||
|
||||
public SourceServerQuery( ServerList.Server server, IPAddress address, int queryPort )
|
||||
{
|
||||
Server = server;
|
||||
endPoint = new IPEndPoint( address, queryPort );
|
||||
return rules;
|
||||
}
|
||||
|
||||
Current.Add( this );
|
||||
static byte[] readBuffer = new byte[1024 * 8];
|
||||
|
||||
IsRunning = true;
|
||||
IsSuccessful = false;
|
||||
thread = new System.Threading.Thread( ThreadedStart );
|
||||
thread.Start();
|
||||
}
|
||||
static async Task<byte[]> Receive( UdpClient client )
|
||||
{
|
||||
byte[][] packets = null;
|
||||
byte packetNumber = 0, packetCount = 1;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if ( !IsRunning )
|
||||
{
|
||||
Current.Remove( this );
|
||||
Server.OnServerRulesReceiveFinished( rules, IsSuccessful );
|
||||
}
|
||||
}
|
||||
do
|
||||
{
|
||||
var result = await client.ReceiveAsync();
|
||||
var buffer = result.Buffer;
|
||||
|
||||
private void ThreadedStart( object obj )
|
||||
{
|
||||
try
|
||||
{
|
||||
using ( udpClient = new UdpClient() )
|
||||
{
|
||||
udpClient.Client.SendTimeout = 3000;
|
||||
udpClient.Client.ReceiveTimeout = 3000;
|
||||
udpClient.Connect( endPoint );
|
||||
using ( var br = new BinaryReader( new MemoryStream( buffer ) ) )
|
||||
{
|
||||
var header = br.ReadInt32();
|
||||
|
||||
GetRules();
|
||||
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" );
|
||||
}
|
||||
|
||||
IsSuccessful = true;
|
||||
}
|
||||
}
|
||||
catch ( System.Exception )
|
||||
{
|
||||
IsSuccessful = false;
|
||||
}
|
||||
if ( packets == null ) packets = new byte[packetCount][];
|
||||
|
||||
udpClient = null;
|
||||
IsRunning = false;
|
||||
}
|
||||
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 ) );
|
||||
|
||||
void GetRules()
|
||||
{
|
||||
GetChallengeData();
|
||||
var combinedData = Combine( packets );
|
||||
return combinedData;
|
||||
}
|
||||
|
||||
_challengeBytes[0] = A2S_RULES;
|
||||
Send( _challengeBytes );
|
||||
var ruleData = Receive();
|
||||
private static async Task<byte[]> GetChallengeData( UdpClient client )
|
||||
{
|
||||
await Send( client, A2S_SERVERQUERY_GETCHALLENGE );
|
||||
|
||||
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 challengeData = await Receive( client );
|
||||
|
||||
var numRules = br.ReadUInt16();
|
||||
for ( int index = 0; index < numRules; index++ )
|
||||
{
|
||||
rules.Add( br.ReadNullTerminatedUTF8String( readBuffer ), br.ReadNullTerminatedUTF8String( readBuffer ) );
|
||||
}
|
||||
}
|
||||
if ( challengeData[0] != 0x41 )
|
||||
throw new Exception( "Invalid Challenge" );
|
||||
|
||||
}
|
||||
return challengeData;
|
||||
}
|
||||
|
||||
byte[] readBuffer = new byte[1024 * 4];
|
||||
static byte[] sendBuffer = new byte[1024];
|
||||
|
||||
private byte[] Receive()
|
||||
{
|
||||
byte[][] packets = null;
|
||||
byte packetNumber = 0, packetCount = 1;
|
||||
static async Task Send( UdpClient client, byte[] message )
|
||||
{
|
||||
sendBuffer[0] = 0xFF;
|
||||
sendBuffer[1] = 0xFF;
|
||||
sendBuffer[2] = 0xFF;
|
||||
sendBuffer[3] = 0xFF;
|
||||
|
||||
do
|
||||
{
|
||||
var result = udpClient.Receive( ref endPoint );
|
||||
Buffer.BlockCopy( message, 0, sendBuffer, 4, message.Length );
|
||||
|
||||
using ( var br = new BinaryReader( new MemoryStream( result ) ) )
|
||||
{
|
||||
var header = br.ReadInt32();
|
||||
await client.SendAsync( sendBuffer, message.Length + 4 );
|
||||
}
|
||||
|
||||
if ( header == -1 )
|
||||
{
|
||||
var unsplitdata = new byte[result.Length - br.BaseStream.Position];
|
||||
Buffer.BlockCopy( result, (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" );
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
if ( packets == null ) packets = new byte[packetCount][];
|
||||
|
||||
var data = new byte[result.Length - br.BaseStream.Position];
|
||||
Buffer.BlockCopy( result, (int)br.BaseStream.Position, data, 0, data.Length );
|
||||
packets[packetNumber] = data;
|
||||
}
|
||||
}
|
||||
while ( packets.Any( p => p == null ) );
|
||||
|
||||
var combinedData = Combine( packets );
|
||||
return combinedData;
|
||||
}
|
||||
|
||||
private void GetChallengeData()
|
||||
{
|
||||
if ( _challengeBytes != null ) return;
|
||||
|
||||
Send( A2S_SERVERQUERY_GETCHALLENGE );
|
||||
|
||||
var challengeData = Receive();
|
||||
|
||||
if ( challengeData[0] != 0x41 )
|
||||
throw new Exception( "Invalid Challenge" );
|
||||
|
||||
_challengeBytes = challengeData;
|
||||
}
|
||||
|
||||
byte[] sendBuffer = new byte[1024];
|
||||
|
||||
private void Send( byte[] message )
|
||||
{
|
||||
sendBuffer[0] = 0xFF;
|
||||
sendBuffer[1] = 0xFF;
|
||||
sendBuffer[2] = 0xFF;
|
||||
sendBuffer[3] = 0xFF;
|
||||
|
||||
Buffer.BlockCopy( message, 0, sendBuffer, 4, message.Length );
|
||||
|
||||
udpClient.Send( sendBuffer, message.Length + 4 );
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if ( thread != null && thread.IsAlive )
|
||||
{
|
||||
thread.Abort();
|
||||
}
|
||||
|
||||
thread = null;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
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 Steamworks
|
||||
{
|
||||
internal abstract class SteamInterface
|
||||
{
|
||||
public IntPtr Self;
|
||||
public IntPtr VTable;
|
||||
|
||||
public virtual string InterfaceName => null;
|
||||
public bool IsValid => Self != IntPtr.Zero && VTable != IntPtr.Zero;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
if ( SteamClient.IsValid )
|
||||
{
|
||||
InitClient();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( SteamServer.IsValid )
|
||||
{
|
||||
InitServer();
|
||||
return;
|
||||
}
|
||||
|
||||
throw new System.Exception( "Trying to initialize Steam Interface but Steam not initialized" );
|
||||
}
|
||||
|
||||
public void InitClient()
|
||||
{
|
||||
|
||||
//
|
||||
// There's an issue for us using FindOrCreateUserInterface on Rust.
|
||||
// We have a different appid for our staging branch, but we use Rust's
|
||||
// appid so we can still test with the live data/setup. The issue is
|
||||
// if we run the staging branch and get interfaces using FindOrCreate
|
||||
// then some callbacks don't work. I assume this is because these interfaces
|
||||
// have already been initialized using the old appid pipe, but since I
|
||||
// can't see inside Steam this is just a gut feeling. Either way using
|
||||
// CreateInterface doesn't seem to have caused any fires, so we'll just use that.
|
||||
//
|
||||
|
||||
//
|
||||
// var pipe = SteamAPI.GetHSteamPipe();
|
||||
//
|
||||
|
||||
Self = SteamInternal.CreateInterface( InterfaceName );
|
||||
|
||||
if ( Self == IntPtr.Zero )
|
||||
{
|
||||
var user = SteamAPI.GetHSteamUser();
|
||||
Self = SteamInternal.FindOrCreateUserInterface( user, InterfaceName );
|
||||
}
|
||||
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Couldn't find interface {InterfaceName}" );
|
||||
|
||||
VTable = Marshal.ReadIntPtr( Self, 0 );
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Invalid VTable for {InterfaceName}" );
|
||||
|
||||
InitInternals();
|
||||
SteamClient.WatchInterface( this );
|
||||
}
|
||||
|
||||
public void InitServer()
|
||||
{
|
||||
var user = SteamGameServer.GetHSteamUser();
|
||||
Self = SteamInternal.FindOrCreateGameServerInterface( user, InterfaceName );
|
||||
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Couldn't find server interface {InterfaceName}" );
|
||||
|
||||
VTable = Marshal.ReadIntPtr( Self, 0 );
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Invalid VTable for server {InterfaceName}" );
|
||||
|
||||
InitInternals();
|
||||
SteamServer.WatchInterface( this );
|
||||
}
|
||||
|
||||
public virtual void InitUserless()
|
||||
{
|
||||
Self = SteamInternal.FindOrCreateUserInterface( 0, InterfaceName );
|
||||
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Couldn't find interface {InterfaceName}" );
|
||||
|
||||
VTable = Marshal.ReadIntPtr( Self, 0 );
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Invalid VTable for {InterfaceName}" );
|
||||
|
||||
InitInternals();
|
||||
}
|
||||
|
||||
internal virtual void Shutdown()
|
||||
{
|
||||
Self = IntPtr.Zero;
|
||||
VTable = IntPtr.Zero;
|
||||
|
||||
}
|
||||
|
||||
public abstract void InitInternals();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace 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
|
||||
{
|
||||
internal IntPtr ptr;
|
||||
|
||||
public unsafe static implicit operator string( Utf8StringPointer p )
|
||||
{
|
||||
return ConvertPtrToString(p.ptr);
|
||||
}
|
||||
|
||||
public unsafe static string ConvertPtrToString(IntPtr ptr)
|
||||
{
|
||||
if (ptr == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
var bytes = (byte*)ptr;
|
||||
|
||||
var dataLen = 0;
|
||||
while (dataLen < 1024 * 1024 * 64)
|
||||
{
|
||||
if (bytes[dataLen] == 0)
|
||||
break;
|
||||
|
||||
dataLen++;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(bytes, dataLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
public static partial class Utility
|
||||
{
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReadNullTerminatedUTF8String( this BinaryReader br, byte[] buffer = null )
|
||||
{
|
||||
if ( buffer == null )
|
||||
buffer = new byte[1024];
|
||||
|
||||
byte chr;
|
||||
int i = 0;
|
||||
while ( (chr = br.ReadByte()) != 0 && i < buffer.Length )
|
||||
{
|
||||
buffer[i] = chr;
|
||||
i++;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString( buffer, 0, i );
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user