2
0
mirror of https://github.com/Facepunch/Facepunch.Steamworks.git synced 2025-05-17 08:58:11 +03:00

Compare commits

..

No commits in common. "master" and "v0.1" have entirely different histories.
master ... v0.1

369 changed files with 17138 additions and 70348 deletions
.editorconfig
.github
.gitignoreCompileFix.bat
Facepunch.Steamworks.Test
Facepunch.Steamworks.sln
Facepunch.Steamworks

@ -1,100 +0,0 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.cs]
indent_style = tab
indent_size = tab
tab_size = 4
# New line preferences
end_of_line = crlf
insert_final_newline = true
#### C# Coding Conventions ####
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion
# Code-block preferences
csharp_prefer_braces = true:silent
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = no_change
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = true
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = true
csharp_space_between_parentheses = control_flow_statements
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true

@ -1,34 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: garrynewman
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Set x to y
2. Run for x minutes
3. Call x
4. See error
**Calling Code**
```
// The code you're using to call into Steamworks
Steamworks.DoBug();
```
**Expected behavior**
A clear and concise description of what you expected to happen.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Unity: [e.g Unity 2019.3]
**Additional context**
Add any other context about the problem here.

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

@ -1,10 +0,0 @@
---
name: Something Else
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

@ -1,42 +0,0 @@
name: Build All
on: [push]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4.2.2
- name: Setup dotnet 6.0.x
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: '6.0.x'
include-prerelease: true
- name: Restore Win64
run: dotnet restore Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj
- name: Restore Win32
run: dotnet restore Facepunch.Steamworks\Facepunch.Steamworks.Win32.csproj
- name: Restore Posix
run: dotnet restore Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj
- name: Build Win64
run: dotnet build Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj
- name: Build Win32
run: dotnet build Facepunch.Steamworks\Facepunch.Steamworks.Win32.csproj
- name: Build Posix
run: dotnet build Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj
- name: Build Win64 Release
run: dotnet build Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj --configuration Release
- name: Build Win32 Release
run: dotnet build Facepunch.Steamworks\Facepunch.Steamworks.Win32.csproj --configuration Release
- name: Build Posix Release
run: dotnet build Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj --configuration Release
- uses: actions/upload-artifact@v4.4.3
with:
name: Compiled Files
path: Facepunch.Steamworks/bin

8
.gitignore vendored

@ -55,14 +55,11 @@ Facepunch.Steamworks.Test/bin/Release/Facepunch.Steamworks.Test.dll
Facepunch.Steamworks.Test/bin/Release/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
*.user
*.cache
*.idea
*.vscode
TestResults
obj
Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.Api.dll
Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.dll
Facepunch.Steamworks/bin/Release/Facepunch.Steamworks.dll
Facepunch.Steamworks/bin
*.opendb
*.db
Facepunch.Steamworks.dll
@ -70,8 +67,3 @@ Facepunch.Steamworks.Test.dll
*UnitTestFramework.dll
mscorlib.dll
*.nlp
packages
Generator/bin
*.XML
.vs
Facepunch.Steamworks.Test/bin/**

@ -1,5 +0,0 @@
dotnet restore .\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj
dotnet restore .\Facepunch.Steamworks\Facepunch.Steamworks.Win32.csproj
dotnet restore .\Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj
dotnet restore .\Facepunch.Steamworks.Test\Facepunch.Steamworks.TestWin32.csproj
dotnet restore .\Facepunch.Steamworks.Test\Facepunch.Steamworks.TestWin64.csproj

@ -1,121 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class AppTest
{
[AssemblyInitialize]
public static void AssemblyInit( TestContext context )
{
Steamworks.Dispatch.OnDebugCallback = ( type, str, server ) =>
{
Console.WriteLine( $"[Callback {type} {(server ? "server" : "client")}]" );
Console.WriteLine( str );
Console.WriteLine( $"" );
};
Steamworks.Dispatch.OnException = ( e ) =>
{
Console.Error.WriteLine( e.Message );
Console.Error.WriteLine( e.StackTrace );
Assert.Fail( e.Message );
};
//
// Init Client
//
Steamworks.SteamClient.Init( 252490 );
//
// Init Server
//
var serverInit = new SteamServerInit( "rust", "Rusty Mode" )
{
GamePort = 28015,
Secure = true,
QueryPort = 28016
};
Steamworks.SteamServer.Init( 252490, serverInit );
//
// Needs to happen before LogOnAnonymous
//
SteamNetworkingSockets.RequestFakeIP();
SteamServer.LogOnAnonymous();
}
[TestMethod]
public void GameLangauge()
{
var gl = SteamApps.GameLanguage;
Assert.IsNotNull( gl );
Assert.IsTrue( gl.Length > 3 );
Console.WriteLine( $"{gl}" );
}
[TestMethod]
public void AppInstallDir()
{
var str = SteamApps.AppInstallDir();
Assert.IsNotNull( str );
Assert.IsTrue( str.Length > 3 );
Console.WriteLine( $"{str}" );
}
[TestMethod]
public void AppOwner()
{
var steamid = SteamApps.AppOwner;
Assert.IsTrue( steamid.Value > 70561197960279927 );
Assert.IsTrue( steamid.Value < 80561197960279927 );
Console.WriteLine( $"{steamid.Value}" );
}
[TestMethod]
public void InstalledDepots()
{
var depots = SteamApps.InstalledDepots().ToArray();
Assert.IsNotNull( depots );
Assert.IsTrue( depots.Length > 0 );
foreach ( var depot in depots )
{
Console.WriteLine( $"{depot.Value}" );
}
}
[TestMethod]
public async Task GetFileDetails()
{
var fileinfo = await SteamApps.GetFileDetailsAsync( "RustClient.exe" );
Console.WriteLine( $"fileinfo.SizeInBytes: {fileinfo?.SizeInBytes}" );
Console.WriteLine( $"fileinfo.Sha1: {fileinfo?.Sha1}" );
Console.WriteLine( $"fileinfo.Flags: {fileinfo?.Flags}" );
}
[TestMethod]
public void CommandLine()
{
var cl = SteamApps.CommandLine;
Console.WriteLine( $"CommandLine: {cl}" );
}
}
}

@ -1,59 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Steamworks
{
[DeploymentItem("steam_api64.dll")]
[DeploymentItem("steam_api.dll")]
[TestClass]
public class ClanTest
{
[TestMethod]
public void GetName()
{
var clan = new Clan(103582791433666425);
Assert.AreEqual("Steamworks Development", clan.Name);
}
[TestMethod]
public void GetClanTag()
{
var clan = new Clan(103582791433666425);
Assert.AreEqual("SteamworksDev", clan.Tag);
}
[TestMethod]
public async Task GetOwner()
{
var clan = new Clan(103582791433666425);
await clan.RequestOfficerList();
Assert.AreNotEqual(new SteamId(), clan.Owner.Id);
}
[TestMethod]
public void GetOfficers()
{
var clan = new Clan(103582791433666425);
foreach (var officer in clan.GetOfficers())
{
Console.WriteLine($"{officer.Name} : {officer.Id}");
}
}
[TestMethod]
public async Task RequestOfficerList()
{
var clan = new Clan(103582791433666425);
bool res = await clan.RequestOfficerList();
Assert.AreEqual(true, res);
}
}
}

@ -0,0 +1,253 @@
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem( Config.LibraryName + ".dll" )]
[DeploymentItem( "steam_appid.txt" )]
public partial class Client
{
[TestMethod]
public void Init()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
}
}
[TestMethod]
public void Init_50()
{
for ( int i = 0; i < 50; i++ )
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
}
GC.Collect();
}
}
[TestMethod]
public void Name()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var username = client.Username;
Console.WriteLine( username );
Assert.IsTrue( client.IsValid );
Assert.IsNotNull( username );
}
}
[TestMethod]
public void SteamId()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var steamid = client.SteamId;
Console.WriteLine( steamid );
Assert.IsTrue( client.IsValid );
Assert.AreNotEqual( 0, steamid );
}
}
[TestMethod]
public void AuthSessionTicket()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var ticket = client.Auth.GetAuthSessionTicket();
Assert.IsTrue( ticket != null );
Assert.IsTrue( ticket.Handle != 0 );
Assert.IsTrue( ticket.Data.Length > 0 );
ticket.Cancel();
Assert.IsTrue( ticket.Handle == 0 );
}
}
[TestMethod]
public void VoiceOptimalSampleRate()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var rate = client.Voice.OptimalSampleRate;
Assert.AreNotEqual( rate, 0 );
}
}
[TestMethod]
public void Update()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
for( int i=0; i<1024; i++ )
{
client.Update();
}
}
}
static MemoryStream decompressStream = new MemoryStream();
[TestMethod]
public void GetVoice()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
int unCompressed = 0;
int compressed = 0;
client.Voice.OnCompressedData = ( ptr, length ) =>
{
compressed += length;
if ( !client.Voice.Decompress( ptr, 0, length, decompressStream ) )
{
Assert.Fail( "Decompress returned false" );
}
};
client.Voice.OnUncompressedData = ( ptr, length ) =>
{
unCompressed += length;
};
client.Voice.WantsRecording = true;
var sw = Stopwatch.StartNew();
while ( sw.Elapsed.TotalSeconds < 3 )
{
client.Update();
System.Threading.Thread.Sleep( 10 );
}
Assert.AreNotEqual( unCompressed, 0 );
Assert.AreNotEqual( compressed, 0 );
// Should really be > 0 if the mic was getting audio
Console.WriteLine( "unCompressed: {0}", unCompressed );
Console.WriteLine( "compressed: {0}", compressed );
}
}
[TestMethod]
public void GetVoice_Compressed_Only()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
int compressed = 0;
client.Voice.OnCompressedData = ( ptr, length ) =>
{
compressed += length;
};
client.Voice.WantsRecording = true;
var sw = Stopwatch.StartNew();
while ( sw.Elapsed.TotalSeconds < 3 )
{
client.Update();
System.Threading.Thread.Sleep( 10 );
}
Assert.AreNotEqual( compressed, 0 );
Console.WriteLine( "compressed: {0}", compressed );
}
}
[TestMethod]
public void GetVoice_UnCompressed_Only()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
int unCompressed = 0;
client.Voice.OnUncompressedData = ( ptr, length ) =>
{
unCompressed += length;
};
client.Voice.WantsRecording = true;
var sw = Stopwatch.StartNew();
while ( sw.Elapsed.TotalSeconds < 3 )
{
client.Update();
System.Threading.Thread.Sleep( 10 );
}
Assert.AreNotEqual( unCompressed, 0 );
// Should really be > 0 if the mic was getting audio
Console.WriteLine( "unCompressed: {0}", unCompressed );
}
}
[TestMethod]
public void InventoryDefinitions()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsNotNull( client.Inventory.Definitions );
Assert.AreNotEqual( 0, client.Inventory.Definitions.Length );
foreach ( var i in client.Inventory.Definitions )
{
Console.WriteLine( "{0}: {1}", i.Id, i.Name );
}
}
}
[TestMethod]
public void InventoryItemList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
bool CallbackCalled = false;
// OnUpdate hsould be called when we receive a list of our items
client.Inventory.OnUpdate = () => { CallbackCalled = true; };
// tell steam to download the items
client.Inventory.Refresh();
// Wait for the items
var timeout = Stopwatch.StartNew();
while ( client.Inventory.Items == null )
{
client.Update();
System.Threading.Thread.Sleep( 1000 );
if ( timeout.Elapsed.TotalSeconds > 5 )
break;
}
// make sure callback was called
Assert.IsTrue( CallbackCalled );
// Make sure items are valid
foreach ( var item in client.Inventory.Items )
{
Assert.IsNotNull( item );
Assert.IsNotNull( item.Definition );
Console.WriteLine( item.Definition.Name + " - " + item.Id );
}
}
}
}
}

@ -0,0 +1,92 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
namespace Facepunch.Steamworks.Test
{
[DeploymentItem( Config.LibraryName + ".dll" )]
[DeploymentItem( "steam_appid.txt" )]
[TestClass]
public class Friends
{
[TestMethod]
public void FriendList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
client.Friends.Refresh();
Assert.IsNotNull( client.Friends.All );
foreach ( var friend in client.Friends.All )
{
Console.WriteLine( "{0}: {1} (Friend:{2}) (Blocked:{3})", friend.Id, friend.Name, friend.IsFriend, friend.IsBlocked );
}
}
}
[TestMethod]
public void FriendListWithoutRefresh()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
foreach ( var friend in client.Friends.All )
{
Console.WriteLine( "{0}: {1} (Friend:{2}) (Blocked:{3})", friend.Id, friend.Name, friend.IsFriend, friend.IsBlocked );
}
}
}
[TestMethod]
public void Avatar()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
var friend = client.Friends.All.First();
var img = client.Friends.GetAvatar( Steamworks.Friends.AvatarSize.Medium, friend.Id );
Assert.AreEqual( img.Width, 64 );
Assert.AreEqual( img.Height, 64 );
while ( !img.IsLoaded && !img.IsError )
{
client.Update();
System.Threading.Thread.Sleep( 10 );
}
Assert.AreEqual( img.Data.Length, img.Width * img.Height * 4 );
DrawImage( img );
}
}
public static void DrawImage( Image img )
{
var grad = " -:+#";
for ( int y = 0; y<img.Height; y++ )
{
var str = "";
for ( int x = 0; x < img.Width; x++ )
{
var p = img.GetPixel( x, y );
var brightness = 1 - ((float)(p.r + p.g + p.b) / (255.0f * 3.0f));
var c = (int) ((grad.Length) * brightness);
str += grad[c];
}
Console.WriteLine( str );
}
}
}
}

@ -0,0 +1,108 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem( Config.LibraryName + ".dll" )]
[DeploymentItem( "steam_appid.txt" )]
public partial class Networking
{
[TestMethod]
public void PeerToPeerSend()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var TestString = "This string will be transformed to bytes, sent over the Steam P2P network, then converted back to a string.";
var OutputReceived = false;
var data = Encoding.UTF8.GetBytes( TestString );
client.Networking.OnP2PData = ( steamid, ms, channel ) =>
{
var str = Encoding.UTF8.GetString( ms.GetBuffer() );
Assert.AreEqual( str, TestString );
Assert.AreEqual( steamid, client.SteamId );
OutputReceived = true;
};
client.Networking.OnIncomingConnection = ( steamid ) =>
{
Console.WriteLine( "Incoming P2P Connection: " + steamid );
return true;
};
client.Networking.OnConnectionFailed = ( steamid, error ) =>
{
Console.WriteLine( "Connection Error: " + steamid + " - " + error );
};
client.Networking.SendP2PPacket( client.SteamId, data, data.Length );
while( true )
{
Thread.Sleep( 10 );
client.Update();
if ( OutputReceived )
break;
}
}
}
[TestMethod]
public void PeerToPeerFailure()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var TestString = "This string will be transformed to bytes, sent over the Steam P2P network, then converted back to a string.";
var TimeoutReceived = false;
var data = Encoding.UTF8.GetBytes( TestString );
client.Networking.OnIncomingConnection = ( steamid ) =>
{
Console.WriteLine( "Incoming P2P Connection: " + steamid );
return true;
};
client.Networking.OnConnectionFailed = ( steamid, error ) =>
{
Console.WriteLine( "Connection Error: " + steamid + " - " + error );
TimeoutReceived = true;
};
ulong rand = (ulong) new Random().Next( 1024 * 16 );
// Send to an invalid, not listening steamid
if ( !client.Networking.SendP2PPacket( client.SteamId + rand, data, data.Length ) )
{
Console.WriteLine( "Couldn't send packet" );
return;
}
var sw = Stopwatch.StartNew();
while ( true )
{
Thread.Sleep( 10 );
client.Update();
//
// Timout is usually around 15 seconds
//
if ( TimeoutReceived )
break;
if ( sw.Elapsed.TotalSeconds > 30 )
{
Assert.Fail( "Didn't time out" );
}
}
}
}
}
}

@ -1,43 +0,0 @@
using System;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
/*
namespace Facepunch.Steamworks.Test
{
public partial class Server
{
[TestMethod]
public void StatsGet()
{
using ( var server = new Facepunch.Steamworks.Server( 252490, new ServerInit( "rust", "Rust" ) ) )
{
Assert.IsTrue( server.IsValid );
server.LogOnAnonymous();
ulong MySteamId = 76561197960279927;
bool GotStats = false;
server.Stats.Refresh( MySteamId, (steamid, success) =>
{
GotStats = true;
Assert.IsTrue( success );
var deathsInCallback = server.Stats.GetInt( MySteamId, "deaths", -1 );
Console.WriteLine( "deathsInCallback: {0}", deathsInCallback );
Assert.IsTrue( deathsInCallback > 0 );
} );
server.UpdateWhile( () => !GotStats );
var deaths = server.Stats.GetInt( MySteamId, "deaths", -1 );
Console.WriteLine( "deathsInCallback: {0}", deaths );
Assert.IsTrue( deaths > 0 );
}
}
}
}
*/

@ -0,0 +1,357 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem( Config.LibraryName + ".dll" )]
[DeploymentItem( "steam_appid.txt" )]
public partial class ServerList
{
[TestMethod]
public void InternetList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var filter = new Facepunch.Steamworks.ServerList.Filter();
filter.Add( "appid", client.AppId.ToString() );
filter.Add( "gamedir", "rust" );
filter.Add( "secure", "1" );
var query = client.ServerList.Internet( filter );
for ( int i = 0; i < 1000; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 10 );
foreach ( var s in query.Responded )
{
Assert.AreEqual( s.AppId, client.AppId );
Assert.AreEqual( s.GameDir, "rust" );
}
if ( query.Finished )
break;
}
Console.WriteLine( "Responded: " + query.Responded.Count.ToString() );
Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() );
foreach ( var server in query.Responded.Take( 20 ) )
{
Console.WriteLine( "{0} {1}", server.AddressString, server.Name );
}
query.Dispose();
for ( int i = 0; i < 100; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 1 );
}
}
}
[TestMethod]
public void MultipleInternetList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var queries = new List<Facepunch.Steamworks.ServerList.Request>();
var filter = new Facepunch.Steamworks.ServerList.Filter();
filter.Add( "map", "barren" );
for ( int i = 0; i < 10; i++ )
queries.Add( client.ServerList.Internet( filter ) );
for ( int i = 0; i < 100; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 5 );
if ( queries.Any( x => x.Finished ) )
break;
}
foreach ( var query in queries )
{
Console.WriteLine( "Responded: " + query.Responded.Count.ToString() );
Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() );
client.Update();
query.Dispose();
client.Update();
}
}
}
[TestMethod]
public void Filters()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var filter = new Facepunch.Steamworks.ServerList.Filter();
filter.Add( "map", "barren" );
var query = client.ServerList.Internet( filter );
while ( true )
{
client.Update();
System.Threading.Thread.Sleep( 2 );
if ( query.Finished )
break;
}
foreach ( var x in query.Responded )
{
Assert.AreEqual( x.Map.ToLower(), "barren" );
}
query.Dispose();
for ( int i = 0; i < 100; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 1 );
}
}
}
[TestMethod]
public void HistoryList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var query = client.ServerList.History();
while ( true )
{
client.Update();
System.Threading.Thread.Sleep( 2 );
if ( query.Finished )
break;
}
Console.WriteLine( "Responded: " + query.Responded.Count.ToString() );
Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() );
foreach ( var x in query.Responded )
{
Console.WriteLine( x.Map );
}
query.Dispose();
for ( int i = 0; i < 100; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 1 );
}
}
}
[TestMethod]
public void CustomList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var servers = new List<string>();
servers.Add( "158.85.101.20:28015" );
servers.Add( "158.85.101.20:28022" );
servers.Add( "173.192.176.171:28615" );
servers.Add( "109.95.212.35:28215" );
servers.Add( "109.95.212.35:28115" );
servers.Add( "27.50.72.176:28015" );
servers.Add( "109.95.212.40:28015" );
servers.Add( "212.38.168.149:28215" );
servers.Add( "27.50.72.167:28215" );
servers.Add( "85.236.105.7:28215" );
servers.Add( "107.182.233.216:28215" );
servers.Add( "85.236.105.11:28215" );
servers.Add( "109.95.211.198:28215" );
servers.Add( "8.26.94.190:28015" );
servers.Add( "221.121.151.37:28215" );
servers.Add( "161.202.144.216:28215" );
servers.Add( "107.182.230.181:28215" );
servers.Add( "107.182.231.134:27101" );
servers.Add( "107.182.233.181:27101" );
servers.Add( "78.129.153.47:27101" );
servers.Add( "109.95.211.206:27101" );
servers.Add( "169.57.142.73:27101" );
servers.Add( "221.121.154.147:27101" );
servers.Add( "31.216.52.44:30015" );
servers.Add( "109.169.94.17:28215" );
servers.Add( "109.169.94.17:28315" );
servers.Add( "109.169.94.17:28015" );
servers.Add( "41.0.11.167:27141" );
servers.Add( "78.129.153.47:27131" );
servers.Add( "109.95.211.206:27111" );
servers.Add( "107.182.231.134:27111" );
servers.Add( "198.27.70.162:28015" );
servers.Add( "198.27.70.162:28215" );
servers.Add( "198.27.70.162:28115" );
servers.Add( "169.57.142.73:27111" );
servers.Add( "221.121.154.147:27111" );
servers.Add( "107.182.233.181:27111" );
servers.Add( "78.129.153.47:27111" );
servers.Add( "109.95.211.215:28015" );
servers.Add( "50.23.131.208:28015" );
servers.Add( "50.23.131.208:28115" );
servers.Add( "50.23.131.208:28215" );
servers.Add( "63.251.114.37:28215" );
servers.Add( "63.251.114.37:28115" );
servers.Add( "63.251.114.37:28015" );
servers.Add( "149.202.89.85:27101" );
servers.Add( "149.202.89.85:27111" );
servers.Add( "149.202.89.85:27131" );
servers.Add( "8.26.94.147:27101" );
servers.Add( "8.26.94.147:27111" );
servers.Add( "8.26.94.147:27121" );
servers.Add( "159.8.147.197:28025" );
servers.Add( "162.248.88.203:27038" );
servers.Add( "162.248.88.203:28091" );
servers.Add( "74.91.119.142:28069" );
servers.Add( "162.248.88.203:25063" );
servers.Add( "64.251.7.189:28115" );
servers.Add( "64.251.7.189:28015" );
servers.Add( "216.52.0.170:28215" );
servers.Add( "217.147.91.80:28215" );
servers.Add( "63.251.112.121:28215" );
servers.Add( "162.248.88.203:28074" );
servers.Add( "74.91.119.142:27095" );
servers.Add( "95.172.92.176:28065" );
servers.Add( "192.223.26.55:26032" );
servers.Add( "40.114.199.6:28085" );
servers.Add( "95.172.92.176:27095" );
servers.Add( "216.52.0.172:28015" );
servers.Add( "216.52.0.171:28115" );
servers.Add( "27.50.72.179:28015" );
servers.Add( "27.50.72.180:28115" );
servers.Add( "221.121.158.203:28015" );
servers.Add( "63.251.242.246:28015" );
servers.Add( "85.236.105.51:28015" );
servers.Add( "85.236.105.47:28015" );
servers.Add( "209.95.60.216:28015" );
servers.Add( "212.38.168.14:28015" );
servers.Add( "217.147.91.138:28015" );
servers.Add( "31.216.52.42:28015" );
servers.Add( "107.182.226.225:28015" );
servers.Add( "109.95.211.69:28015" );
servers.Add( "209.95.56.13:28015" );
servers.Add( "173.244.192.101:28015" );
servers.Add( "221.121.158.201:28115" );
servers.Add( "63.251.242.245:28115" );
servers.Add( "85.236.105.50:28115" );
servers.Add( "85.236.105.46:28115" );
servers.Add( "209.95.60.217:28115" );
servers.Add( "212.38.168.13:28115" );
servers.Add( "217.147.91.139:28115" );
servers.Add( "107.182.226.224:28115" );
servers.Add( "109.95.211.14:28115" );
servers.Add( "109.95.211.16:28115" );
servers.Add( "109.95.211.17:28115" );
servers.Add( "209.95.56.14:28115" );
servers.Add( "173.244.192.100:28115" );
servers.Add( "209.95.60.218:28215" );
servers.Add( "109.95.211.13:28215" );
servers.Add( "109.95.211.15:28215" );
servers.Add( "31.216.52.41:29015" );
var query = client.ServerList.Custom( servers );
for ( int i = 0; i < 1000; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 20 );
if ( query.Finished )
break;
}
Console.WriteLine( "Responded: " + query.Responded.Count.ToString() );
Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() );
foreach ( var s in query.Responded )
{
Console.WriteLine( "{0} - {1}", s.AddressString, s.Name );
}
query.Dispose();
}
}
[TestMethod]
public void Rules()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var filter = new Facepunch.Steamworks.ServerList.Filter();
filter.Add( "appid", client.AppId.ToString() );
filter.Add( "gamedir", "rust" );
filter.Add( "secure", "1" );
using ( var query = client.ServerList.Internet( filter ) )
{
for ( int i = 0; i < 1000; i++ )
{
GC.Collect();
client.Update();
GC.Collect();
System.Threading.Thread.Sleep( 10 );
if ( query.Responded.Count > 20 )
break;
if ( query.Finished )
break;
}
query.Dispose();
foreach ( var server in query.Responded.Take( 20 ) )
{
GC.Collect();
server.FetchRules();
GC.Collect();
int i = 0;
while ( !server.HasRules )
{
i++;
GC.Collect();
client.Update();
GC.Collect();
System.Threading.Thread.Sleep( 2 );
if ( i > 100 )
break;
}
if ( server.HasRules )
{
foreach ( var rule in server.Rules )
{
Console.WriteLine( rule.Key + " = " + rule.Value );
}
}
}
}
}
}
}
}

@ -0,0 +1,73 @@
using System;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem( Config.LibraryName + ".dll" )]
[DeploymentItem( "steam_appid.txt" )]
public class Stats
{
[TestMethod]
public void UpdateStats()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
client.Stats.UpdateStats();
}
}
[TestMethod]
public void UpdateSUpdateGlobalStatstats()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
client.Stats.UpdateGlobalStats( 1 );
client.Stats.UpdateGlobalStats( 3 );
client.Stats.UpdateGlobalStats( 7 );
}
}
[TestMethod]
public void GetClientFloat()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var v = client.Stats.GetFloat( "deaths" );
Console.WriteLine( v );
}
}
[TestMethod]
public void GetClientInt()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var v = client.Stats.GetInt( "deaths" );
Console.WriteLine( v );
}
}
[TestMethod]
public void GetGlobalFloat()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var v = client.Stats.GetGlobalFloat( "deaths" );
Console.WriteLine( v );
}
}
[TestMethod]
public void GetGlobalInt()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var v = client.Stats.GetGlobalInt( "deaths" );
Console.WriteLine( v );
}
}
}
}

@ -0,0 +1,425 @@
using System;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem( Config.LibraryName + ".dll" )]
[DeploymentItem( "steam_appid.txt" )]
public class WorkshopTest
{
[TestMethod]
public void Query()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
var Query = client.Workshop.CreateQuery();
Query.Run();
// Block, wait for result
// (don't do this in realtime)
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
// results
Console.WriteLine( "Searching" );
Query.Order = Workshop.Order.RankedByTextSearch;
Query.QueryType = Workshop.QueryType.MicrotransactionItems;
Query.SearchText = "black";
Query.RequireTags.Add( "LongTShirt Skin" );
Query.Run();
// Block, wait for result
// (don't do this in realtime)
Query.Block();
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
foreach ( var item in Query.Items )
{
Console.WriteLine( "{0}", item.Title );
Console.WriteLine( "\t WebsiteViews: {0}", item.WebsiteViews );
Console.WriteLine( "\t VotesUp: {0}", item.VotesUp );
Console.WriteLine( "\t PreviewUrl: {0}", item.PreviewImageUrl );
}
}
}
[TestMethod]
public void QueryTagRequire()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
using ( var Query = client.Workshop.CreateQuery() )
{
Query.RequireTags.Add( "LongTShirt Skin" );
Query.Run();
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
foreach ( var item in Query.Items )
{
Console.WriteLine( "{0}", item.Title );
Console.WriteLine( "\t{0}", string.Join( ";", item.Tags ) );
Assert.IsTrue( item.Tags.Contains( "LongTShirt Skin" ) );
}
}
}
}
[TestMethod]
public void QueryTagRequireMultiple()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
using ( var Query = client.Workshop.CreateQuery() )
{
Query.RequireTags.Add( "LongTShirt Skin" );
Query.RequireTags.Add( "version2" );
Query.RequireAllTags = true;
Query.Run();
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
foreach ( var item in Query.Items )
{
Console.WriteLine( "{0}", item.Title );
Console.WriteLine( "\t{0}", string.Join( ";", item.Tags ) );
Assert.IsTrue( item.Tags.Contains( "LongTShirt Skin" ) );
Assert.IsTrue( item.Tags.Contains( "version2" ) );
}
}
}
}
[TestMethod]
public void QueryTagExclude()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
using ( var Query = client.Workshop.CreateQuery() )
{
Query.RequireTags.Add( "LongTShirt Skin" );
Query.ExcludeTags.Add( "version2" );
Query.Run();
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
foreach ( var item in Query.Items )
{
Console.WriteLine( "{0}", item.Title );
Console.WriteLine( "\t{0}", string.Join( ";", item.Tags ) );
Assert.IsTrue( item.Tags.Contains( "LongTShirt Skin" ) );
Assert.IsFalse( item.Tags.Contains( "version2" ) );
}
}
}
}
[TestMethod]
public void QueryFile()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
using ( var Query = client.Workshop.CreateQuery() )
{
Query.FileId.Add( 751993251 );
Query.Run();
Assert.IsTrue( Query.IsRunning );
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.AreEqual( Query.TotalResults, 1 );
Assert.AreEqual( Query.Items.Length, 1 );
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
Assert.AreEqual<ulong>( Query.Items[0].Id, 751993251 );
}
}
}
[TestMethod]
public void QueryFiles()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
using ( var Query = client.Workshop.CreateQuery() )
{
Query.FileId.Add( 751993251 );
Query.FileId.Add( 747266909 );
Query.Run();
Assert.IsTrue( Query.IsRunning );
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.AreEqual( Query.TotalResults, 2 );
Assert.AreEqual( Query.Items.Length, 2 );
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
Assert.IsTrue( Query.Items.Any( x => x.Id == 751993251 ) );
Assert.IsTrue( Query.Items.Any( x => x.Id == 747266909 ) );
}
}
}
[TestMethod]
public void Query_255()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
using ( var Query = client.Workshop.CreateQuery() )
{
Query.PerPage = 255;
Query.Run();
Assert.IsTrue( Query.IsRunning );
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.AreEqual( Query.Items.Length, 255 );
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
}
}
}
[TestMethod]
public void Query_28()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
using ( var Query = client.Workshop.CreateQuery() )
{
Query.PerPage = 28;
Query.Run();
Query.Block();
var firstPage = Query.Items;
Assert.AreEqual( firstPage.Length, 28 );
Console.WriteLine( "Page 2" );
Query.Page++;
Query.Run();
Query.Block();
var secondPage = Query.Items;
Assert.AreEqual( secondPage.Length, 28 );
Console.WriteLine( "Page 3" );
Query.Page++;
Query.Run();
Query.Block();
var thirdPage = Query.Items;
Assert.AreEqual( thirdPage.Length, 28 );
foreach ( var i in firstPage )
{
Assert.IsFalse( secondPage.Any( x => x.Id == i.Id ) );
Assert.IsFalse( thirdPage.Any( x => x.Id == i.Id ) );
}
foreach ( var i in secondPage )
{
Assert.IsFalse( firstPage.Any( x => x.Id == i.Id ) );
Assert.IsFalse( thirdPage.Any( x => x.Id == i.Id ) );
}
foreach ( var i in thirdPage )
{
Assert.IsFalse( secondPage.Any( x => x.Id == i.Id ) );
Assert.IsFalse( firstPage.Any( x => x.Id == i.Id ) );
}
}
}
}
[TestMethod]
public void DownloadFile()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
using ( var Query = client.Workshop.CreateQuery() )
{
Query.FileId.Add( 661319648 );
Query.Run();
Assert.IsTrue( Query.IsRunning );
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.AreEqual( Query.TotalResults, 1 );
Assert.AreEqual( Query.Items.Length, 1 );
var item = Query.Items[0];
if ( !item.Installed )
{
item.Download();
while ( item.Downloading )
{
Thread.Sleep( 500 );
client.Update();
Console.WriteLine( "Download Progress: {0}", item.DownloadProgress );
}
}
Assert.IsNotNull( item.Directory );
Assert.AreNotEqual( 0, item.Size );
Console.WriteLine( "item.Installed: {0}", item.Installed );
Console.WriteLine( "item.Downloading: {0}", item.Downloading );
Console.WriteLine( "item.DownloadPending: {0}", item.DownloadPending );
Console.WriteLine( "item.Directory: {0}", item.Directory );
Console.WriteLine( "item.Size: {0}mb", (item.Size / 1024 / 1024) );
}
}
}
[TestMethod]
[TestCategory( "Run Manually" )]
public void CreatePublish()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
var item = client.Workshop.CreateItem( Workshop.ItemType.Microtransaction );
item.Title = "Facepunch.Steamworks Unit test";
item.Tags.Add( "Apple" );
item.Tags.Add( "Banana" );
item.Publish();
while ( item.Publishing )
{
client.Update();
Thread.Sleep( 100 );
}
Assert.IsFalse( item.Publishing );
Assert.AreNotEqual( 0, item.Id );
Assert.IsNull( item.Error );
Console.WriteLine( "item.Id: {0}", item.Id );
item.Delete();
}
}
[TestMethod]
public void UserQuery()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
var Query = client.Workshop.CreateQuery();
Query.UserId = 76561197960279927;
Query.UserQueryType = Workshop.UserQueryType.Published;
Query.Run();
// Block, wait for result
// (don't do this in realtime)
Query.Block();
Assert.IsFalse( Query.IsRunning );
Assert.IsTrue( Query.TotalResults > 0 );
Assert.IsTrue( Query.Items.Length > 0 );
Console.WriteLine( "Query.TotalResults: {0}", Query.TotalResults );
Console.WriteLine( "Query.Items.Length: {0}", Query.Items.Length );
foreach ( var item in Query.Items )
{
Console.WriteLine( "{0}", item.Title );
Assert.AreEqual<ulong>( item.OwnerId, 76561197960279927 );
}
}
}
}
}

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3F6183AD-D966-44F2-A6EB-42E61E591B49}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Facepunch.Steamworks.Test</RootNamespace>
<AssemblyName>Facepunch.Steamworks.Test</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework">
<Private>False</Private>
</Reference>
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="Client\Client.cs" />
<Compile Include="Client\Workshop.cs" />
<Compile Include="Client\Networking.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Client\Friends.cs" />
<Compile Include="Server\Inventory.cs" />
<Compile Include="Server\Server.cs" />
<Compile Include="Server\Stats.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Facepunch.Steamworks\Facepunch.Steamworks.csproj">
<Project>{dc2d9fa9-f005-468f-8581-85c79f4e0034}</Project>
<Name>Facepunch.Steamworks</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Client\Serverlist.cs" />
<Compile Include="Client\Stats.cs" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -1,55 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;TEST_WIN32</DefineConstants>
<PlatformTarget>x86</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>TRACE;TEST_WIN32</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<OutputPath>bin\x64\Debug\</OutputPath>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<OutputPath>bin\x86\Debug\</OutputPath>
<PlatformTarget>x64</PlatformTarget>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<PlatformTarget>x64</PlatformTarget>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Facepunch.Steamworks\Facepunch.Steamworks.Win32.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="steam_api.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0-beta4" />
<PackageReference Include="MSTest.TestFramework" Version="2.0.0-beta4" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.2-beta1" />
</ItemGroup>
<ItemGroup>
<Compile Remove="ClanTest.cs" />
</ItemGroup>
</Project>

@ -1,54 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;TEST_WIN64</DefineConstants>
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<OutputPath>bin\x64\Debug\</OutputPath>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<OutputPath>bin\x86\Debug\</OutputPath>
<PlatformTarget>x64</PlatformTarget>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<PlatformTarget>x64</PlatformTarget>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="steam_api64.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0-beta4" />
<PackageReference Include="MSTest.TestFramework" Version="2.0.0-beta4" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.2-beta1" />
</ItemGroup>
<ItemGroup>
<Compile Remove="ClanTest.cs" />
</ItemGroup>
</Project>

@ -1,154 +0,0 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using System.Threading.Tasks;
using Steamworks.Data;
namespace Steamworks
{
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
[TestClass]
public class FriendsTest
{
[TestMethod]
public void GetFriends()
{
foreach ( var friend in SteamFriends.GetFriends() )
{
Console.WriteLine( $"{friend.Id.Value}: {friend.Name} (Friend:{friend.IsFriend}) (Blocked:{friend.IsBlocked})" );
Console.WriteLine( $" {string.Join( ", ", friend.NameHistory )}" );
// Assert.IsNotNull( friend.GetAvatar( Steamworks.Friends.AvatarSize.Medium ) );
}
}
[TestMethod]
public void GetBlocked()
{
foreach ( var friend in SteamFriends.GetBlocked() )
{
Console.WriteLine( $"{friend.Id.Value}: {friend.Name} (Friend:{friend.IsFriend}) (Blocked:{friend.IsBlocked})" );
Console.WriteLine( $" {string.Join( ", ", friend.NameHistory )}" );
// Assert.IsNotNull( friend.GetAvatar( Steamworks.Friends.AvatarSize.Medium ) );
}
}
[TestMethod]
public async Task GetPlayedWith()
{
foreach ( var friend in SteamFriends.GetPlayedWith() )
{
await friend.RequestInfoAsync();
Console.WriteLine( $"{friend.Id.Value}: {friend.Name} (Friend:{friend.IsFriend}) (Blocked:{friend.IsBlocked})" );
Console.WriteLine( $" {string.Join( ", ", friend.NameHistory )}" );
// Assert.IsNotNull( friend.GetAvatar( Steamworks.Friends.AvatarSize.Medium ) );
}
}
[TestMethod]
public async Task LargeAvatar()
{
ulong id = (ulong)(76561197960279927 + (new Random().Next() % 10000));
var image = await SteamFriends.GetLargeAvatarAsync( id );
if ( !image.HasValue )
return;
Console.WriteLine( $"image.Width {image.Value.Width}" );
Console.WriteLine( $"image.Height {image.Value.Height}" );
DrawImage( image.Value );
}
[TestMethod]
public async Task MediumAvatar()
{
ulong id = (ulong)(76561197960279927 + (new Random().Next() % 10000));
Console.WriteLine( $"Steam: http://steamcommunity.com/profiles/{id}" );
var image = await SteamFriends.GetMediumAvatarAsync( id );
if ( !image.HasValue )
return;
Console.WriteLine( $"image.Width {image.Value.Width}" );
Console.WriteLine( $"image.Height {image.Value.Height}" );
DrawImage( image.Value );
}
[TestMethod]
public async Task SmallAvatar()
{
ulong id = (ulong)(76561197960279927 + (new Random().Next() % 10000));
var image = await SteamFriends.GetSmallAvatarAsync( id );
if ( !image.HasValue )
return;
Console.WriteLine( $"image.Width {image.Value.Width}" );
Console.WriteLine( $"image.Height {image.Value.Height}" );
DrawImage( image.Value );
}
[TestMethod]
public async Task GetFriendsAvatars()
{
foreach ( var friend in SteamFriends.GetFriends() )
{
Console.WriteLine( $"{friend.Id.Value}: {friend.Name}" );
var image = await friend.GetSmallAvatarAsync();
if ( image.HasValue )
{
DrawImage( image.Value );
}
// Assert.IsNotNull( friend.GetAvatar( Steamworks.Friends.AvatarSize.Medium ) );
}
}
[TestMethod]
public async Task OpenWebOverlay()
{
if ( SteamUtils.IsOverlayEnabled )
Console.WriteLine( "Overlay Is Enabled" );
else
Console.WriteLine( "Overlay Is Not Enabled" );
SteamFriends.OpenWebOverlay( "https://www.google.com/" );
await Task.Delay( 2000 );
}
public static void DrawImage( Image img )
{
var grad = " -:+#";
for ( int y = 0; y < img.Height; y++ )
{
var str = "";
for ( int x = 0; x < img.Width; x++ )
{
var p = img.GetPixel( x, y );
var brightness = 1 - ((float)(p.r + p.g + p.b) / (255.0f * 3.0f));
var c = (int)((grad.Length) * brightness);
if ( c > 3 ) c = 3;
str += grad[c];
}
Console.WriteLine( str );
}
}
}
}

@ -1,32 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class GameServerStatsTest
{
static SteamId Garry = 76561197960279927;
[TestMethod]
public async Task GetAchievement()
{
var result = await SteamServerStats.RequestUserStatsAsync( Garry );
Assert.AreEqual( result, Result.OK );
var value = SteamServerStats.GetAchievement( Garry, "COLLECT_100_WOOD" );
Assert.IsTrue( value );
value = SteamServerStats.GetAchievement( Garry, "ACHIVEMENT_THAT_DOESNT_EXIST" );
Assert.IsFalse( value );
}
}
}

@ -1,128 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
[TestClass]
public partial class GameServerTest
{
[TestMethod]
public void Init()
{
SteamServer.DedicatedServer = true;
SteamServer.DedicatedServer = false;
}
[TestMethod]
public async Task PublicIp()
{
while ( true )
{
var ip = SteamServer.PublicIp;
if ( ip == null )
{
await Task.Delay( 10 );
continue;
}
Assert.IsNotNull( ip );
Console.WriteLine( ip.ToString() );
break;
}
}
[TestMethod]
public async Task BeginAuthSession()
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
bool finished = false;
string failed = null;
AuthResponse response = AuthResponse.AuthTicketInvalidAlreadyUsed;
//
// Clientside calls this function, gets ticket
//
var clientTicket = SteamUser.GetAuthSessionTicket( NetIdentity.LocalHost );
//
// The client sends this data to the server along with their steamid
//
var ticketData = clientTicket.Data;
var clientSteamId = SteamClient.SteamId;
//
// Server listens to auth responses from Gabe
//
SteamServer.OnValidateAuthTicketResponse += ( steamid, ownerid, rsponse ) =>
{
finished = true;
response = rsponse;
if ( steamid == 0 )
failed = $"steamid is 0! {steamid} != {ownerid} ({rsponse})";
if ( ownerid == 0 )
failed = $"ownerid is 0! {steamid} != {ownerid} ({rsponse})";
if ( steamid != ownerid )
failed = $"Steamid and Ownerid are different! {steamid} != {ownerid} ({rsponse})";
};
//
// Server gets the ticket, starts authing
//
if ( !SteamServer.BeginAuthSession( ticketData, clientSteamId ) )
{
Assert.Fail( "BeginAuthSession returned false, called bullshit without even having to check with Gabe" );
}
//
// Wait for that to go through steam
//
while ( !finished )
{
if ( stopwatch.Elapsed.TotalSeconds > 5 )
throw new System.Exception( "Took too long waiting for AuthSessionResponse.OK" );
await Task.Delay( 10 );
}
Assert.AreEqual( response, AuthResponse.OK );
if ( failed != null )
Assert.Fail( failed );
finished = false;
stopwatch = System.Diagnostics.Stopwatch.StartNew();
//
// The client is leaving, and now wants to cancel the ticket
//
Assert.AreNotEqual( 0, clientTicket.Handle );
clientTicket.Cancel();
//
// We should get another callback
//
while ( !finished )
{
if ( stopwatch.Elapsed.TotalSeconds > 5 )
throw new System.Exception( "Took too long waiting for AuthSessionResponse.AuthTicketCanceled" );
await Task.Delay( 10 );
}
if ( failed != null )
Assert.Fail( failed );
//Assert.AreEqual( response, AuthResponse.AuthTicketCanceled );
}
}
}

@ -1,32 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
[DeploymentItem( "controller_config/game_actions_252490.vdf" )]
public class InputTest
{
[TestMethod]
public void ControllerList()
{
foreach ( var controller in SteamInput.Controllers )
{
Console.Write( $"Controller: {controller}" );
var dstate = controller.GetDigitalState( "fire" );
var astate = controller.GetAnalogState( "Move" );
}
}
}
}

@ -1,223 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class InventoryTest
{
[TestMethod]
public async Task LoadItemDefinitionsAsync()
{
var result = await SteamInventory.WaitForDefinitions( 5 );
Assert.IsTrue( result );
result = await SteamInventory.WaitForDefinitions( 5 );
Assert.IsTrue( result );
}
[TestMethod]
public async Task GetDefinitions()
{
await SteamInventory.WaitForDefinitions();
Assert.IsNotNull( SteamInventory.Definitions );
foreach ( var def in SteamInventory.Definitions )
{
Console.WriteLine( $"[{def.Id:0000000000}] {def.Name} [{def.Type}]" );
}
}
[TestMethod]
public async Task GetDefinitionsWithPrices()
{
var defs = await SteamInventory.GetDefinitionsWithPricesAsync();
foreach ( var def in defs )
{
Console.WriteLine( $"[{def.Id:0000000000}] {def.Name} [{def.LocalPriceFormatted}]" );
}
}
[TestMethod]
public async Task GetAllItems()
{
await SteamInventory.WaitForDefinitions();
var result = await SteamInventory.GetAllItemsAsync();
Assert.IsTrue( result.HasValue );
Assert.IsTrue( result.Value.ItemCount > 0 );
using ( result )
{
var items = result.Value.GetItems( true );
Assert.IsNotNull( items );
foreach ( var item in items )
{
Console.WriteLine( $"{item.Id} / {item.DefId} / {item.Quantity} / {item.Def?.Name} /[{item.IsNoTrade}|{item.IsRemoved}|{item.IsConsumed}] " );
foreach ( var prop in item.Properties )
{
Console.WriteLine( $" {prop.Key} : {prop.Value}" );
}
}
}
}
[TestMethod]
public async Task GetItemSpecialProperties()
{
await SteamInventory.WaitForDefinitions();
var result = await SteamInventory.GetAllItemsAsync();
Assert.IsTrue( result.HasValue );
Assert.IsTrue( result.Value.ItemCount > 0 );
using ( result )
{
var items = result.Value.GetItems( true );
Assert.IsNotNull( items );
foreach ( var item in items )
{
Console.WriteLine( $"{item.Id} / {item.DefId} / {item.Quantity} / {item.Def?.Name} " );
Console.WriteLine( $" Acquired: {item.Acquired}" );
Console.WriteLine( $" Origin: {item.Origin}" );
}
}
}
[TestMethod]
public async Task GetAllItemsMultipleTimes()
{
await SteamInventory.WaitForDefinitions();
var fresult = await SteamInventory.GetAllItemsAsync();
Assert.IsTrue( fresult.HasValue );
Assert.IsTrue( fresult.Value.ItemCount > 0 );
await Task.Delay( 1000 );
var result = await SteamInventory.GetAllItemsAsync();
Assert.IsTrue( result.HasValue );
Assert.IsTrue( result.Value.GetItems().Length == fresult.Value.ItemCount );
await Task.Delay( 1000 );
result = await SteamInventory.GetAllItemsAsync();
Assert.IsTrue( result.HasValue );
Assert.IsTrue( result.Value.ItemCount == fresult.Value.ItemCount );
}
[TestMethod]
public async Task Items()
{
SteamInventory.GetAllItems();
await SteamInventory.WaitForDefinitions();
while ( SteamInventory.Items == null )
{
await Task.Delay( 10 );
}
Assert.IsNotNull( SteamInventory.Items );
foreach ( var item in SteamInventory.Items )
{
Console.WriteLine( $"{item.Id} / {item.DefId} / {item.Quantity} / {item.Def.Name}" );
}
}
[TestMethod]
public async Task GetExchanges()
{
var result = await SteamInventory.WaitForDefinitions( 5 );
Assert.IsTrue( result );
foreach ( var def in SteamInventory.Definitions )
{
var exchangelist = def.GetRecipes();
if ( exchangelist == null ) continue;
foreach ( var exchange in exchangelist )
{
Assert.AreEqual( exchange.Result, def );
Console.WriteLine( $"{def.Name}:" );
foreach ( var item in exchange.Ingredients )
{
Console.WriteLine( $" {item.Count} x {item.Definition?.Name ?? item.DefinitionId.ToString()}" );
}
Console.WriteLine( $"" );
}
}
}
[TestMethod]
public async Task Serialize()
{
await SteamInventory.WaitForDefinitions();
var result = await SteamInventory.GetAllItemsAsync();
Assert.IsTrue( result.HasValue );
var data = result.Value.Serialize();
Assert.IsNotNull( data );
Console.WriteLine( string.Join( "", data.Select( x => x.ToString( "x" ) ) ) );
}
[TestMethod]
public async Task Deserialize()
{
await SteamInventory.WaitForDefinitions();
byte[] data;
int itemCount;
// Serialize
{
var result = await SteamInventory.GetAllItemsAsync();
Assert.IsTrue( result.HasValue );
itemCount = result.Value.ItemCount;
data = result.Value.Serialize();
Assert.IsNotNull( data );
result.Value.Dispose();
}
await Task.Delay( 2000 );
// Deserialize
{
var result = await SteamInventory.DeserializeAsync( data );
Assert.IsTrue( result.HasValue );
Assert.AreEqual( itemCount, result.Value.ItemCount );
result.Value.Dispose();
}
}
}
}

@ -1,163 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public partial class NetworkingSocketsTest
{
void DebugOutput( NetDebugOutput type, string text )
{
Console.WriteLine( $"[NET:{type}]\t\t{text}" );
}
[TestMethod]
public async Task CreateRelayServer()
{
SteamNetworkingUtils.DebugLevel = NetDebugOutput.Everything;
SteamNetworkingUtils.OnDebugOutput += DebugOutput;
var si = SteamNetworkingSockets.CreateRelaySocket<TestSocketInterface>();
Console.WriteLine( $"Created Socket: {si}" );
// Give it a second for something to happen
await Task.Delay( 1000 );
si.Close();
}
[TestMethod]
public async Task CreateNormalServer()
{
SteamNetworkingUtils.DebugLevel = NetDebugOutput.Everything;
SteamNetworkingUtils.OnDebugOutput += DebugOutput;
var si = SteamNetworkingSockets.CreateNormalSocket<TestSocketInterface>( Data.NetAddress.AnyIp( 21893 ) );
Console.WriteLine( $"Created Socket: {si}" );
// Give it a second for something to happen
await Task.Delay( 1000 );
si.Close();
}
[TestMethod]
public async Task CreateRelayServerFakeIP()
{
SteamNetworkingUtils.DebugLevel = NetDebugOutput.Everything;
SteamNetworkingUtils.OnDebugOutput += DebugOutput;
var si = SteamNetworkingSockets.CreateRelaySocketFakeIP<TestSocketInterface>();
Console.WriteLine( $"Created Socket: {si}" );
// Give it a second for something to happen
await Task.Delay( 1000 );
si.Close();
}
[TestMethod]
public async Task RelayEndtoEnd()
{
SteamNetworkingUtils.InitRelayNetworkAccess();
SteamNetworkingUtils.DebugLevel = NetDebugOutput.Warning;
SteamNetworkingUtils.OnDebugOutput += DebugOutput;
// For some reason giving steam a couple of seconds here
// seems to prevent it returning null connections from ConnectNormal
await Task.Delay( 2000 );
Console.WriteLine( $"----- Creating Socket Relay Socket.." );
var socket = SteamNetworkingSockets.CreateRelaySocket<TestSocketInterface>( 6 );
var server = socket.RunAsync();
await Task.Delay( 1000 );
Console.WriteLine( $"----- Connecting To Socket via SteamId ({SteamClient.SteamId})" );
var connection = SteamNetworkingSockets.ConnectRelay<TestConnectionInterface>( SteamClient.SteamId, 6 );
var client = connection.RunAsync();
await Task.WhenAll( server, client );
}
[TestMethod]
public async Task NormalEndtoEnd()
{
SteamNetworkingUtils.DebugLevel = NetDebugOutput.Everything;
SteamNetworkingUtils.OnDebugOutput += DebugOutput;
// For some reason giving steam a couple of seconds here
// seems to prevent it returning null connections from ConnectNormal
await Task.Delay( 2000 );
//
// Start the server
//
Console.WriteLine( "CreateNormalSocket" );
var socket = SteamNetworkingSockets.CreateNormalSocket<TestSocketInterface>( NetAddress.AnyIp( 12445 ) );
var server = socket.RunAsync();
//
// Start the client
//
Console.WriteLine( "ConnectNormal" );
var connection = SteamNetworkingSockets.ConnectNormal<TestConnectionInterface>( NetAddress.From( "127.0.0.1", 12445 ) );
var client = connection.RunAsync();
await Task.WhenAll( server, client );
}
[TestMethod]
public async Task RelayEndtoEndFakeIP()
{
SteamNetworkingUtils.InitRelayNetworkAccess();
SteamNetworkingUtils.DebugLevel = NetDebugOutput.Warning;
SteamNetworkingUtils.OnDebugOutput += DebugOutput;
// For some reason giving steam a couple of seconds here
// seems to prevent it returning null connections from ConnectNormal
await Task.Delay( 2000 );
Console.WriteLine( $"----- Creating Socket Relay Socket.." );
var socket = SteamNetworkingSockets.CreateRelaySocketFakeIP<TestSocketInterface>();
var server = socket.RunAsync();
await Task.Delay( 1000 );
Console.WriteLine( $"----- Retrieving Fake IP.." );
SteamNetworkingSockets.GetFakeIP( 0, out NetAddress address );
Console.WriteLine( $"----- Connecting To Socket via Fake IP ({address})" );
var connection = SteamNetworkingSockets.ConnectNormal<TestConnectionInterface>( address );
var client = connection.RunAsync();
await Task.WhenAll( server, client );
}
[TestMethod]
public void NetAddressTest()
{
{
var n = NetAddress.From( "127.0.0.1", 12445 );
Assert.AreEqual( n.ToString(), "127.0.0.1:12445" );
}
{
var n = NetAddress.AnyIp( 5543 );
Assert.AreEqual( n.ToString(), "[::]:5543" );
}
}
}
}

@ -1,130 +0,0 @@
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
public partial class NetworkingSocketsTest
{
private class TestConnectionInterface : ConnectionManager
{
public override void OnConnectionChanged( ConnectionInfo data )
{
Console.WriteLine( $"[Connection][{Connection}] [{data.State}]" );
base.OnConnectionChanged( data );
}
public override void OnConnecting( ConnectionInfo data )
{
Console.WriteLine( $" - OnConnecting" );
base.OnConnecting( data );
}
/// <summary>
/// Client is connected. They move from connecting to Connections
/// </summary>
public override void OnConnected( ConnectionInfo data )
{
Console.WriteLine( $" - OnConnected" );
base.OnConnected( data );
}
/// <summary>
/// The connection has been closed remotely or disconnected locally. Check data.State for details.
/// </summary>
public override void OnDisconnected( ConnectionInfo data )
{
Console.WriteLine( $" - OnDisconnected" );
base.OnDisconnected( data );
}
internal async Task RunAsync()
{
Console.WriteLine( "[Connection] RunAsync" );
var sw = System.Diagnostics.Stopwatch.StartNew();
Console.WriteLine( "[Connection] Connecting" );
while ( Connecting )
{
await Task.Delay( 10 );
if ( sw.Elapsed.TotalSeconds > 10 )
break;
}
if ( !Connected )
{
Console.WriteLine( "[Connection] Couldn't connect!" );
Console.WriteLine( Connection.DetailedStatus() );
return;
}
Console.WriteLine( "[Connection] Hey We're Connected!" );
sw = System.Diagnostics.Stopwatch.StartNew();
while ( Connected )
{
Receive();
await Task.Delay( 100 );
if ( sw.Elapsed.TotalSeconds > 30 )
{
Assert.Fail( "Client Took Too Long" );
break;
}
}
}
public override unsafe void OnMessage( IntPtr data, int size, long messageNum, long recvTime, int channel )
{
// We're only sending strings, so it's fine to read this like this
var str = Utility.Utf8NoBom.GetString( (byte*) data, size );
Console.WriteLine( $"[Connection][{messageNum}][{recvTime}][{channel}] \"{str}\"" );
if ( str.Contains( "Hello" ) )
{
Console.WriteLine( $"[Connection][{messageNum}][{recvTime}][{channel}] Sending: Hello, How are you!?" );
Connection.SendMessage( "Hello, How are you!?" );
Console.WriteLine( $"[Connection][{messageNum}][{recvTime}][{channel}] Sending: How do you like 20 messages in a row?" );
Connection.SendMessage( "How do you like 20 messages in a row?" );
var connections = new[] { Connection };
var results = new Result[1];
for ( int i=0; i<20; i++ )
{
Console.WriteLine( $"[Connection][{messageNum}][{recvTime}][{channel}] Sending: BLAMMO {i}!" );
SendMessages( connections, connections.Length, $"BLAMMO {i}!", results: results );
Assert.AreEqual( Result.OK, results[0] );
}
Connection.Flush();
}
if ( str.Contains( "status" ))
{
Console.WriteLine( Connection.DetailedStatus() );
}
if ( str.Contains( "how about yourself" ) )
{
Connection.SendMessage( "I'm great, but I have to go now, bye." );
}
if ( str.Contains( "hater" ) )
{
Close();
}
}
}
}
}

@ -1,137 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
public partial class NetworkingSocketsTest
{
private class TestSocketInterface : SocketManager
{
public bool HasFinished = false;
public override void OnConnectionChanged( Connection connection, ConnectionInfo data )
{
Console.WriteLine( $"[Socket{Socket}][connection:{connection}][data.Identity:{data.Identity}] [data.State:{data.State}]" );
base.OnConnectionChanged( connection, data );
}
public override void OnConnecting( Connection connection, ConnectionInfo data )
{
Console.WriteLine( $" - OnConnecting" );
base.OnConnecting( connection, data );
}
/// <summary>
/// Client is connected. They move from connecting to Connections
/// </summary>
public override void OnConnected( Connection connection, ConnectionInfo data )
{
Console.WriteLine( $"" );
Console.WriteLine( $"Socket -> OnConnected:" );
Console.WriteLine( $" data.Address: {data.Address}" );
Console.WriteLine( $" data.Identity: {data.Identity}" );
Console.WriteLine( $" data.Identity.Steamid: {data.Identity.SteamId}" );
Console.WriteLine( $" data.Identity.IsIpAddress: {data.Identity.IsIpAddress}" );
Console.WriteLine( $" data.Identity.IsLocalHost: {data.Identity.IsLocalHost}" );
Console.WriteLine( $" data.Identity.IsSteamId: {data.Identity.IsSteamId}" );
Console.WriteLine( $" data.Identity.Address: {data.Identity.Address}" );
Console.WriteLine( $" data.Identity.Address.Address: {data.Identity.Address.Address}" );
Console.WriteLine( $" data.Identity.Address.Port: {data.Identity.Address.Port}" );
Console.WriteLine( $"" );
base.OnConnected( connection, data );
}
/// <summary>
/// The connection has been closed remotely or disconnected locally. Check data.State for details.
/// </summary>
public override void OnDisconnected( Connection connection, ConnectionInfo data )
{
Console.WriteLine( $" - OnDisconnected" );
base.OnDisconnected( connection, data );
}
internal async Task RunAsync()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
while ( Connected.Count == 0 )
{
await Task.Delay( 10 );
if ( sw.Elapsed.TotalSeconds > 10 )
{
Assert.Fail( "Client Took Too Long To Connect" );
break;
}
}
await Task.Delay( 1000 );
var singleClient = Connected.First();
singleClient.SendMessage( "Hey?" );
await Task.Delay( 100 );
singleClient.SendMessage( "Anyone?" );
await Task.Delay( 100 );
singleClient.SendMessage( "What's this?" );
await Task.Delay( 100 );
singleClient.SendMessage( "What's your status?" );
await Task.Delay( 10 );
singleClient.SendMessage( "Greetings!!??" );
await Task.Delay( 100 );
singleClient.SendMessage( "Hello Client!?" );
sw = System.Diagnostics.Stopwatch.StartNew();
Console.WriteLine( $"Socket: Listening" );
while ( Connected.Contains( singleClient ) )
{
Receive();
await Task.Delay( 100 );
if ( sw.Elapsed.TotalSeconds > 30 )
{
Console.WriteLine( "Socket: This all took too long - throwing an exception" );
Assert.Fail( "Socket Took Too Long" );
break;
}
}
Console.WriteLine( $"Socket: Closing connection because {Connected.Count()} Connected" );
await Task.Delay( 1000 );
Close();
}
public override unsafe void OnMessage( Connection connection, NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel )
{
// We're only sending strings, so it's fine to read this like this
var str = Utility.Utf8NoBom.GetString( (byte*)data, size );
Console.WriteLine( $"[SOCKET][{connection}[{identity}][{messageNum}][{recvTime}][{channel}] \"{str}\"" );
if ( str.Contains( "Hello, How are you" ) )
{
connection.SendMessage( "I'm great thanks, how about yourself?" );
}
if ( str.Contains( "bye" ) )
{
connection.SendMessage( "See you later, hater." );
connection.Flush();
connection.Close( true, 10, "Said Bye" );
}
}
}
}
}

@ -1,63 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class NetworkUtilsTest
{
static string GarrysLocation = "lhr=4+0,ams=13+1/10+0,par=17+1/12+0,lux=17+1,fra=18+1/18+0,sto=25+2,sto2=26+2,mad=27+2,vie=31+3/30+0,iad=90+9/75+0,sgp=173+17/174+17,gru=200+20/219+0";
[TestMethod]
public async Task LocalPingLocation()
{
await SteamNetworkingUtils.WaitForPingDataAsync();
for ( int i = 0; i < 10; i++ )
{
var pl = SteamNetworkingUtils.LocalPingLocation;
if ( !pl.HasValue )
{
await Task.Delay( 1000 );
continue;
}
Console.WriteLine( $"{i} Seconds Until Result: {pl}" );
return;
}
Assert.Fail();
}
[TestMethod]
public void PingLocationParse()
{
var pl = Data.NetPingLocation.TryParseFromString( GarrysLocation );
Assert.IsTrue( pl.HasValue );
Console.WriteLine( $"Parsed OKAY! {pl}" );
}
[TestMethod]
public async Task GetEstimatedPing()
{
await SteamNetworkingUtils.WaitForPingDataAsync();
var garrysping = Data.NetPingLocation.TryParseFromString( GarrysLocation );
Assert.IsTrue( garrysping.HasValue );
var ping = SteamNetworkingUtils.EstimatePingTo( garrysping.Value );
Assert.IsTrue( ping > 0 );
Console.WriteLine( $"Ping returned: {ping}" );
}
}
}

@ -1,30 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class RemotePlayTest
{
[TestMethod]
public void BasicUsability()
{
Console.WriteLine( $"Sessions: {SteamRemotePlay.SessionCount}" );
var session = SteamRemotePlay.GetSession( 4 );
Assert.IsFalse( session.IsValid );
Assert.IsFalse( session.SteamId.IsValid );
}
}
}

@ -1,79 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class RemoteStorageTest
{
[TestMethod]
public void Quotas()
{
Console.WriteLine( $"SteamRemoteStorage.QuotaBytes: {SteamRemoteStorage.QuotaBytes}" );
Console.WriteLine( $"SteamRemoteStorage.QuotaRemainingBytes: {SteamRemoteStorage.QuotaRemainingBytes}" );
Console.WriteLine( $"SteamRemoteStorage.QuotaUsedBytes: {SteamRemoteStorage.QuotaUsedBytes}" );
}
[TestMethod]
public void IsCloudEnabled()
{
Console.WriteLine( $"SteamRemoteStorage.IsCloudEnabled: {SteamRemoteStorage.IsCloudEnabled}" );
Console.WriteLine( $"SteamRemoteStorage.IsCloudEnabledForAccount: {SteamRemoteStorage.IsCloudEnabledForAccount}" );
Console.WriteLine( $"SteamRemoteStorage.IsCloudEnabledForApp: {SteamRemoteStorage.IsCloudEnabledForApp}" );
}
[TestMethod]
public void FileWrite()
{
var rand = new Random();
var testFile = new byte[1024 * 1024 * 100];
for( int i=0; i< testFile.Length; i++ )
{
testFile[i] = (byte) i;
}
var written = SteamRemoteStorage.FileWrite( "testfile", testFile );
Assert.IsTrue( written );
Assert.IsTrue( SteamRemoteStorage.FileExists( "testfile" ) );
Assert.AreEqual( SteamRemoteStorage.FileSize( "testfile" ), testFile.Length );
}
[TestMethod]
public void FileRead()
{
FileWrite();
var data = SteamRemoteStorage.FileRead( "testfile" );
Assert.IsNotNull( data );
for ( int i = 0; i < data.Length; i++ )
{
Assert.AreEqual( data[i], (byte)i );
}
Assert.AreEqual( SteamRemoteStorage.FileSize( "testfile" ), data.Length );
Assert.AreEqual( SteamRemoteStorage.FileSize( "testfile" ), 1024 * 1024 * 100 );
}
[TestMethod]
public void Files()
{
foreach ( var file in SteamRemoteStorage.Files )
{
Console.WriteLine( $"{file} ({SteamRemoteStorage.FileSize(file)} {SteamRemoteStorage.FileTime( file )})" );
}
}
}
}

@ -0,0 +1,53 @@
using System;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
namespace Facepunch.Steamworks.Test
{
public partial class Server
{
[TestMethod]
public void InventoryDeserialize()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
Assert.IsNull( client.Inventory.SerializedItems );
client.Inventory.Refresh();
//
// Block until we have the items
//
while ( client.Inventory.SerializedItems == null )
{
client.Update();
}
Assert.IsNotNull( client.Inventory.SerializedItems );
Assert.IsTrue( client.Inventory.SerializedItems.Length > 4 );
using ( var server = new Facepunch.Steamworks.Server( 252490, 0, 30002, true, "VersionString" ) )
{
server.LogOnAnonymous();
Assert.IsTrue( server.IsValid );
var result = server.Inventory.Deserialize( client.Inventory.SerializedItems );
Assert.IsTrue( result.Block() );
Assert.IsNotNull( result.Items );
foreach ( var item in result.Items )
{
Console.WriteLine( "Item: {0} ({1})", item.Id, item.DefinitionId );
Console.WriteLine( "Item: {0} ({1})", item.Id, item.DefinitionId );
}
}
}
}
}
}

@ -0,0 +1,115 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[DeploymentItem( Config.LibraryName + ".dll" )]
[DeploymentItem( "steam_appid.txt" )]
[DeploymentItem( "tier0_s.dll" )]
[DeploymentItem( "vstdlib_s.dll" )]
[DeploymentItem( "steamclient.dll" )]
[TestClass]
public partial class Server
{
[TestMethod]
public void Init()
{
using ( var server = new Facepunch.Steamworks.Server( 252490, 30001, 30002, 30003, false, "VersionString" ) )
{
Assert.IsTrue( server.IsValid );
}
}
[TestMethod]
public void AuthCallback()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue( client.IsValid );
var ticket = client.Auth.GetAuthSessionTicket();
var ticketBinary = ticket.Data;
using ( var server = new Facepunch.Steamworks.Server( 252490, 30001, 30002, 30003, true, "VersionString" ) )
{
server.LogOnAnonymous();
Assert.IsTrue( server.IsValid );
var auth = server.Auth;
var Authed = false;
server.Auth.OnAuthChange = ( steamid, ownerid, status ) =>
{
Authed = status == ServerAuth.Status.OK;
Assert.AreEqual( steamid, client.SteamId );
Assert.AreEqual( steamid, ownerid );
Console.WriteLine( "steamid: {0}", steamid );
Console.WriteLine( "ownerid: {0}", ownerid );
Console.WriteLine( "status: {0}", status );
};
for ( int i = 0; i < 16; i++ )
{
System.Threading.Thread.Sleep( 10 );
GC.Collect();
server.Update();
GC.Collect();
client.Update();
GC.Collect();
}
GC.Collect();
if ( !server.Auth.StartSession( ticketBinary, client.SteamId ) )
{
Assert.Fail( "Start Session returned false" );
}
GC.Collect();
//
// Server should receive a ServerAuth.Status.OK
// message via the OnAuthChange callback
//
for ( int i = 0; i< 100; i++ )
{
GC.Collect();
System.Threading.Thread.Sleep( 100 );
GC.Collect();
server.Update();
client.Update();
if ( Authed )
break;
}
Assert.IsTrue( Authed );
//
// Client cancels ticket
//
ticket.Cancel();
//
// Server should receive a ticket cancelled message
//
for ( int i = 0; i < 100; i++ )
{
System.Threading.Thread.Sleep( 100 );
server.Update();
client.Update();
if ( !Authed )
break;
}
Assert.IsTrue( !Authed );
}
}
}
}
}

@ -0,0 +1,34 @@
using System;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
public partial class Server
{
[TestMethod]
public void StatsGet()
{
using ( var server = new Facepunch.Steamworks.Server( 252490, 0, 30002, true, "VersionString" ) )
{
Assert.IsTrue( server.IsValid );
server.LogOnAnonymous();
ulong MySteamId = 76561197960279927;
server.Stats.Refresh( MySteamId );
// TODO - Callback on complete
Thread.Sleep( 2000 );
var deaths = server.Stats.GetInt( MySteamId, "deaths", -1 );
Console.WriteLine( "Deaths: {0}", deaths );
Assert.IsTrue( deaths > 0 );
}
}
}
}

@ -1,251 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public partial class ServerListTest
{
[TestMethod]
public void IpAddressConversions()
{
var ipstr = "185.38.150.40";
var ip = IPAddress.Parse( ipstr );
var ip_int = Utility.IpToInt32( ip );
var ip_back = Utility.Int32ToIp( ip_int );
Console.WriteLine( "ipstr: " + ipstr );
Console.WriteLine( "ip: " + ip );
Console.WriteLine( "ip int: " + ip_int );
Console.WriteLine( "ip_back: " + ip_back );
}
[TestMethod]
public async Task ServerListInternetInterupted()
{
using ( var list = new ServerList.Internet() )
{
var task = list.RunQueryAsync();
await Task.Delay( 1000 );
Console.WriteLine( $"Querying.." );
list.Cancel();
foreach ( var s in list.Responsive )
{
Console.WriteLine( $"{s.Address} {s.Name}" );
}
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
Console.WriteLine( $"task.IsCompleted {task.IsCompleted}" );
}
}
[TestMethod]
public async Task ServerListInternet()
{
using ( var list = new ServerList.Internet() )
{
var success = await list.RunQueryAsync();
Console.WriteLine( $"success {success}" );
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
}
}
// Used to reproduce steam serverlist stopping querying after ~10s around august 2023
[TestMethod]
public async Task RustServerListTest()
{
using ( var list = new ServerList.Internet() )
{
list.AddFilter( "secure", "1" );
list.AddFilter( "and", "1" );
list.AddFilter( "gametype", "v2405" );
list.AddFilter( "appid", "252490" );
list.AddFilter( "gamedir", "rust" );
list.AddFilter( "empty", "1" );
var success = await list.RunQueryAsync( 90 );
Console.WriteLine( $"success {success}" );
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
}
}
[TestMethod]
public async Task SourceQuery()
{
using ( var list = new ServerList.Internet() )
{
var task = list.RunQueryAsync();
await Task.Delay( 1000 );
list.Cancel();
foreach ( var s in list.Responsive.Take( 10 ).ToArray() )
{
Console.WriteLine( $"{s.Name} [{s.Address}]" );
var rules = await s.QueryRulesAsync();
Assert.IsNotNull( rules );
foreach ( var rule in rules )
{
Console.WriteLine( $" {rule.Key} = {rule.Value}" );
}
}
}
}
[TestMethod]
public async Task ServerListLan()
{
using ( var list = new ServerList.LocalNetwork() )
{
var success = await list.RunQueryAsync();
Console.WriteLine( $"success {success}" );
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
}
}
[TestMethod]
public async Task ServerListFavourites()
{
using ( var list = new ServerList.Favourites() )
{
var success = await list.RunQueryAsync();
Console.WriteLine( $"success {success}" );
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
}
}
[TestMethod]
public async Task ServerListFriends()
{
using ( var list = new ServerList.Friends() )
{
var success = await list.RunQueryAsync();
Console.WriteLine( $"success {success}" );
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
}
}
[TestMethod]
public async Task ServerListHistory()
{
using ( var list = new ServerList.History() )
{
var success = await list.RunQueryAsync();
Console.WriteLine( $"success {success}" );
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
}
}
[TestMethod]
public async Task FilterByMap()
{
using ( var list = new ServerList.Internet() )
{
list.AddFilter( "map", "de_dust" );
var success = await list.RunQueryAsync();
Console.WriteLine( $"success {success}" );
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
foreach ( var server in list.Responsive )
{
Assert.AreEqual( server.Map.ToLower(), "de_dust" );
Console.WriteLine( $"[{server.Map}] - {server.Name}" );
}
}
}
[TestMethod]
public async Task ServerListIps()
{
var ips = new string[]
{
"31.186.251.76",
"31.186.251.76",
"31.186.251.76",
"31.186.251.76",
"31.186.251.76",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"139.99.144.70",
"139.99.144.70",
"139.99.144.70",
"139.99.144.70",
"139.99.144.70",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"74.91.119.142",
"95.172.92.176",
"95.172.92.176",
"95.172.92.176",
"95.172.92.176",
"95.172.92.176",
"164.132.205.154",
"164.132.205.154",
"164.132.205.154",
"164.132.205.154",
"164.132.205.154",
};
using ( var list = new ServerList.IpList( ips ) )
{
var success = await list.RunQueryAsync();
Console.WriteLine( $"success {success}" );
Console.WriteLine( $"Found {list.Responsive.Count} Responsive Servers" );
Console.WriteLine( $"Found {list.Unresponsive.Count} Unresponsive Servers" );
Assert.AreNotEqual( list.Responsive.Count, 0 );
foreach ( var server in list.Responsive )
{
Console.WriteLine( $"[{server.Address}:{server.ConnectionPort}] - {server.Name}" );
}
}
}
}
}

@ -1,106 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class SteamMatchmakingTest
{
[TestMethod]
public async Task LobbyList()
{
await CreateLobby();
var list = await SteamMatchmaking.LobbyList
.RequestAsync();
if ( list == null )
{
Console.WriteLine( "No Lobbies Found!" );
return;
}
foreach ( var lobby in list )
{
Console.WriteLine( $"[{lobby.Id}] owned by {lobby.Owner} ({lobby.MemberCount}/{lobby.MaxMembers})" );
}
}
[TestMethod]
public async Task LobbyListWithAtLeastOne()
{
await CreateLobby();
await LobbyList();
}
[TestMethod]
public async Task CreateLobby()
{
var lobbyr = await SteamMatchmaking.CreateLobbyAsync( 32 );
if ( !lobbyr.HasValue )
{
Assert.Fail();
}
var lobby = lobbyr.Value;
lobby.SetPublic();
lobby.SetData( "gametype", "sausage" );
lobby.SetData( "dicks", "unlicked" );
Console.WriteLine( $"lobby: {lobby.Id}" );
foreach ( var entry in lobby.Data )
{
Console.WriteLine( $" - {entry.Key} {entry.Value}" );
}
Console.WriteLine( $"members: {lobby.MemberCount}/{lobby.MaxMembers}" );
Console.WriteLine( $"Owner: {lobby.Owner}" );
Console.WriteLine( $"Owner Is Local Player: {lobby.Owner.IsMe}" );
lobby.SendChatString( "Hello I Love Lobbies" );
}
[TestMethod]
public async Task LobbyChat()
{
SteamMatchmaking.OnChatMessage += ( lbby, member, message ) =>
{
Console.WriteLine( $"[{lbby}] {member}: {message}" );
};
var lobbyr = await SteamMatchmaking.CreateLobbyAsync( 10 );
if ( !lobbyr.HasValue )
Assert.Fail();
var lobby = lobbyr.Value;
lobby.SetPublic();
lobby.SetData( "name", "Dave's Chat Room" );
Console.WriteLine( $"lobby: {lobby.Id}" );
lobby.SendChatString( "Hello Friends, It's me - your big fat daddy" );
await Task.Delay( 50 );
lobby.SendChatString( "What? No love for daddy?" );
await Task.Delay( 500 );
lobby.SendChatString( "Okay I will LEAVE" );
lobby.SendChatString( "BYE FOREVER" );
await Task.Delay( 1000 );
lobby.Leave();
}
}
}

@ -1,37 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class SteamNetworkingTest
{
[TestMethod]
public async Task SendP2PPacket()
{
var sent = SteamNetworking.SendP2PPacket( SteamClient.SteamId, new byte[] { 1, 2, 3 } );
Assert.IsTrue( sent );
while ( !SteamNetworking.IsP2PPacketAvailable() )
{
await Task.Delay( 10 );
}
var packet = SteamNetworking.ReadP2PPacket();
Assert.IsTrue( packet.HasValue );
Assert.AreEqual( packet.Value.SteamId, SteamClient.SteamId );
Assert.AreEqual( packet.Value.Data[1], 2 );
Assert.AreEqual( packet.Value.Data.Length, 3 );
}
}
}

@ -1,183 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class UgcEditor
{
[TestMethod]
public async Task CreateFile()
{
var result = await Ugc.Editor.NewCommunityFile
.WithTitle( "Unit Test Created Item" )
.WithDescription( "This item was created by Facepunch Steamworks unit tests.\n\n" +
"It should have technically been deleted so you should never get to " +
"read this unless something terrible has happened." )
.WithTag( "Arsehole" )
.WithTag( "Spiteful" )
.WithTag( "Fat-Head" )
.SubmitAsync();
Assert.IsTrue( result.Success );
Assert.AreNotEqual( result.FileId.Value, 0 );
var deleted = await SteamUGC.DeleteFileAsync( result.FileId );
Assert.IsTrue( deleted );
}
[TestMethod]
public async Task CreateChineseFile()
{
string fileName = "这是我的项目";
string description = "此项目由Facepunch Steamworks单元测试创建";
var result = await Ugc.Editor.NewCommunityFile
.WithTitle( fileName )
.WithDescription( description )
.WithTag( "Arsehole" )
.WithTag( "Spiteful" )
.WithTag( "Fat-Head" )
.SubmitAsync();
Console.WriteLine( $"Title: {fileName}" );
Console.WriteLine( $"Description: {description}" );
Assert.IsTrue( result.Success );
Assert.AreNotEqual( result.FileId.Value, 0 );
var file = await Steamworks.SteamUGC.QueryFileAsync( result.FileId );
Console.WriteLine( $"FileId: {result.FileId}" );
Console.WriteLine( $"Title: {file.Value.Title}" );
Console.WriteLine( $"Description: {file.Value.Description}" );
Assert.AreEqual( file.Value.Title, fileName );
Assert.AreEqual( file.Value.Description, description );
var deleted = await SteamUGC.DeleteFileAsync( result.FileId );
Assert.IsTrue( deleted );
}
class ProgressBar : IProgress<float>
{
float Value = 0;
public void Report( float value )
{
if ( Value >= value ) return;
Value = value;
Console.WriteLine( value );
}
}
[TestMethod]
public async Task UploadBigishFile()
{
var created = Ugc.Editor.NewCommunityFile
.WithTitle( "Unit Test Upload Item" )
.WithDescription( "This item was created by Facepunch Steamworks unit tests.\n\n" +
"It should have technically been deleted so you should never get to " +
"read this unless something terrible has happened." )
//.WithTag( "Apple" )
//.WithTag( "Banana" )
;
// Make a folder
var testFolder = new System.IO.DirectoryInfo( "WorkshopUpload" );
if ( !testFolder.Exists ) testFolder.Create();
created = created.WithContent( testFolder.FullName );
// Upload a file of random bytes
var rand = new Random();
var testFile = new byte[1024 * 1024 * 32];
rand.NextBytes( testFile );
System.IO.File.WriteAllBytes( testFolder.FullName + "/testfile1.bin", testFile );
Console.WriteLine( testFolder.FullName );
try
{
var done = await created.SubmitAsync( new ProgressBar() );
Assert.IsTrue( done.Success );
Console.WriteLine( "item.Id: {0}", done.FileId );
var deleted = await SteamUGC.DeleteFileAsync( done.FileId );
Assert.IsTrue( deleted );
}
finally
{
System.IO.File.Delete( testFolder.FullName + "/testfile.bin" );
}
}
[TestMethod]
public async Task CreateAndThenEditFile()
{
PublishedFileId fileid;
//
// Make a file
//
{
var result = await Ugc.Editor.NewCommunityFile
.WithTitle( "Unedited File" )
.SubmitAsync();
Assert.IsTrue( result.Success );
Assert.AreNotEqual( result.FileId.Value, 0 );
fileid = result.FileId;
}
await Task.Delay( 1000 );
//
// Edit it
//
{
var editor = new Ugc.Editor( fileid );
editor = editor.WithTitle( "An Edited File" );
var result = await editor.SubmitAsync();
Assert.IsTrue( result.Success );
Assert.AreEqual( result.FileId, fileid );
}
await Task.Delay( 1000 );
//
// Make sure the edited file matches
//
{
var details = await SteamUGC.QueryFileAsync( fileid ) ?? throw new Exception( "Somethign went wrong" );
Assert.AreEqual( details.Id, fileid );
Assert.AreEqual( details.Title, "An Edited File" );
}
//
// Clean up
//
var deleted = await SteamUGC.DeleteFileAsync( fileid );
Assert.IsTrue( deleted );
}
}
}

@ -1,131 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class UgcQueryTests
{
[TestMethod]
public async Task QueryAll()
{
var q = Ugc.Query.All;
var result = await q.GetPageAsync( 1 );
Assert.IsNotNull( result );
Console.WriteLine( $"ResultCount: {result?.ResultCount}" );
Console.WriteLine( $"TotalCount: {result?.TotalCount}" );
}
[TestMethod]
public async Task QueryWithTags()
{
var q = Ugc.Query.All
.WithTag( "Version3" )
.WithTag( "Hunting Bow" )
.MatchAllTags();
var result = await q.GetPageAsync( 1 );
Assert.IsNotNull( result );
Assert.IsTrue( result?.ResultCount > 0 );
Console.WriteLine( $"ResultCount: {result?.ResultCount}" );
Console.WriteLine( $"TotalCount: {result?.TotalCount}" );
foreach ( var entry in result.Value.Entries )
{
Assert.IsTrue( entry.HasTag( "Version3" ), "Has Tag Version3" );
Assert.IsTrue( entry.HasTag( "Hunting Bow" ), "Has Tag HuntingBow" );
}
}
[TestMethod]
public async Task QueryAllFromFriends()
{
var q = Ugc.Query.All
.CreatedByFriends();
var result = await q.GetPageAsync( 1 );
Assert.IsNotNull( result );
Console.WriteLine( $"ResultCount: {result?.ResultCount}" );
Console.WriteLine( $"TotalCount: {result?.TotalCount}" );
foreach ( var entry in result.Value.Entries )
{
Console.WriteLine( $" {entry.Title}" );
}
}
[TestMethod]
public async Task QueryUserOwn()
{
var q = Ugc.Query.All
.WhereUserPublished();
var result = await q.GetPageAsync( 1 );
Assert.IsNotNull( result );
Console.WriteLine( $"ResultCount: {result?.ResultCount}" );
Console.WriteLine( $"TotalCount: {result?.TotalCount}" );
foreach ( var entry in result.Value.Entries )
{
Console.WriteLine( $" {entry.Title}" );
}
}
[TestMethod]
public async Task QueryGarry()
{
var q = Ugc.Query.All
.WhereUserPublished( 76561197960279927 );
var result = await q.GetPageAsync( 1 );
Assert.IsNotNull( result );
Assert.IsTrue( result?.ResultCount > 0 );
Console.WriteLine( $"ResultCount: {result?.ResultCount}" );
Console.WriteLine( $"TotalCount: {result?.TotalCount}" );
foreach ( var entry in result.Value.Entries )
{
Console.WriteLine( $" {entry.Title}" );
}
}
[TestMethod]
public async Task QuerySpecificFile()
{
var item = await SteamUGC.QueryFileAsync( 1734427277 );
Assert.IsTrue( item.HasValue );
Assert.IsNotNull( item.Value.Title );
Console.WriteLine( $"Title: {item?.Title}" );
Console.WriteLine( $"Desc: {item?.Description}" );
Console.WriteLine( $"Tags: {string.Join( ",", item?.Tags )}" );
Console.WriteLine( $"Author: {item?.Owner.Name} [{item?.Owner.Id}]" );
Console.WriteLine( $"PreviewImageUrl: {item?.PreviewImageUrl}" );
Console.WriteLine( $"NumComments: {item?.NumComments}" );
Console.WriteLine( $"Url: {item?.Url}" );
Console.WriteLine( $"Directory: {item?.Directory}" );
Console.WriteLine( $"IsInstalled: {item?.IsInstalled}" );
Console.WriteLine( $"IsAcceptedForUse: {item?.IsAcceptedForUse}" );
Console.WriteLine( $"IsPublic: {item?.IsPublic}" );
Console.WriteLine( $"Created: {item?.Created}" );
Console.WriteLine( $"Updated: {item?.Updated}" );
Console.WriteLine( $"Score: {item?.Score}" );
}
}
}

@ -1,41 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class UgcTest
{
[TestMethod]
public void Download()
{
SteamUGC.Download( 1717844711 );
}
[TestMethod]
public async Task GetInformation()
{
var itemInfo = await Ugc.Item.GetAsync( 1720164672 );
Assert.IsTrue( itemInfo.HasValue );
Console.WriteLine( $"Title: {itemInfo?.Title}" );
Console.WriteLine( $"IsInstalled: {itemInfo?.IsInstalled}" );
Console.WriteLine( $"IsDownloading: {itemInfo?.IsDownloading}" );
Console.WriteLine( $"IsDownloadPending: {itemInfo?.IsDownloadPending}" );
Console.WriteLine( $"IsSubscribed: {itemInfo?.IsSubscribed}" );
Console.WriteLine( $"NeedsUpdate: {itemInfo?.NeedsUpdate}" );
Console.WriteLine( $"Description: {itemInfo?.Description}" );
Console.WriteLine( $"Owner: {itemInfo?.Owner}" );
Console.WriteLine( $"Score: {itemInfo?.Score}" );
Console.WriteLine( $"PreviewImageUrl: {itemInfo?.PreviewImageUrl}" );
}
}
}

@ -1,222 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Steamworks.Data;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class UserStatsTest
{
[TestMethod]
public async Task AchievementList()
{
foreach ( var a in SteamUserStats.Achievements )
{
Console.WriteLine( $"{a.Identifier}" );
Console.WriteLine( $" a.State: {a.State}" );
Console.WriteLine( $" a.UnlockTime: {a.UnlockTime}" );
Console.WriteLine( $" a.Name: {a.Name}" );
Console.WriteLine( $" a.Description: {a.Description}" );
Console.WriteLine( $" a.GlobalUnlocked: {a.GlobalUnlocked}" );
var icon = await a.GetIconAsync();
Console.WriteLine( $" a.Icon: {icon}" );
}
}
[TestMethod]
public async Task PlayerCountAsync()
{
var players = await SteamUserStats.PlayerCountAsync();
Assert.AreNotEqual( players, -1 );
Console.WriteLine( $"players: {players}" );
}
public async Task StoreStats()
{
var result = Result.NotSettled;
SteamUserStats.OnUserStatsStored += ( r ) =>
{
result = r;
};
SteamUserStats.StoreStats();
while ( result == Result.NotSettled )
{
await Task.Delay( 10 );
}
Assert.AreEqual( result, Result.OK );
}
[TestMethod]
public async Task CreateLeaderboard()
{
var leaderboard = await SteamUserStats.FindOrCreateLeaderboardAsync( "Testleaderboard", Data.LeaderboardSort.Ascending, Data.LeaderboardDisplay.Numeric );
Assert.IsTrue( leaderboard.HasValue );
}
[TestMethod]
public async Task FindLeaderboard()
{
var leaderboard = await SteamUserStats.FindLeaderboardAsync( "Testleaderboard" );
Assert.IsTrue( leaderboard.HasValue );
}
[TestMethod]
public async Task SubmitScore()
{
var leaderboard = await SteamUserStats.FindLeaderboardAsync( "Testleaderboard" );
Assert.IsTrue( leaderboard.HasValue );
var result = await leaderboard.Value.SubmitScoreAsync( 576 );
Assert.IsTrue( result.HasValue );
Console.WriteLine( $"result.Changed: {result?.Changed}" );
Console.WriteLine( $"result.OldGlobalRank: {result?.OldGlobalRank}" );
Console.WriteLine( $"result.NewGlobalRank: {result?.NewGlobalRank}" );
Console.WriteLine( $"result.RankChange: {result?.RankChange}" );
Console.WriteLine( $"result.Score: {result?.Score}" );
}
[TestMethod]
public async Task ReplaceScore()
{
var leaderboard = await SteamUserStats.FindLeaderboardAsync( "Testleaderboard" );
Assert.IsTrue( leaderboard.HasValue );
var result = await leaderboard.Value.ReplaceScore( 576 );
Assert.IsTrue( result.HasValue );
Console.WriteLine( $"result.Changed: {result?.Changed}" );
Console.WriteLine( $"result.OldGlobalRank: {result?.OldGlobalRank}" );
Console.WriteLine( $"result.NewGlobalRank: {result?.NewGlobalRank}" );
Console.WriteLine( $"result.RankChange: {result?.RankChange}" );
Console.WriteLine( $"result.Score: {result?.Score}" );
}
[TestMethod]
public async Task GetScoresFromFriends()
{
var leaderboard = await SteamUserStats.FindLeaderboardAsync( "Testleaderboard" );
var friendScores = await leaderboard.Value.GetScoresFromFriendsAsync();
foreach ( var e in friendScores )
{
Console.WriteLine( $"{e.GlobalRank}: {e.Score} {e.User}" );
}
}
[TestMethod]
public async Task GetScoresAroundUserAsync()
{
var leaderboard = await SteamUserStats.FindLeaderboardAsync( "Testleaderboard" );
Assert.IsTrue( leaderboard.HasValue );
for ( int i = 1; i < 10; i++ )
{
// Get entries around user
var relativeScores = await leaderboard.Value.GetScoresAroundUserAsync( -i, i );
Assert.IsNotNull( relativeScores );
Console.WriteLine( $"" );
Console.WriteLine( $"Relative Scores:" );
foreach ( var e in relativeScores )
{
Console.WriteLine( $"{e.GlobalRank}: {e.Score} {e.User}" );
}
}
}
[TestMethod]
public async Task GetScoresAsync()
{
var leaderboard = await SteamUserStats.FindLeaderboardAsync( "Testleaderboard" );
Assert.IsTrue( leaderboard.HasValue );
// Get top 20 global scores
var globalsScores = await leaderboard.Value.GetScoresAsync( 20 );
Assert.IsNotNull( globalsScores );
Console.WriteLine( $"" );
Console.WriteLine( $"Global Scores:" );
foreach ( var e in globalsScores )
{
Console.WriteLine( $"{e.GlobalRank}: {e.Score} {e.User}" );
}
}
[TestMethod]
public void GetStatInt()
{
var deaths = new Stat( "deaths" );
Console.WriteLine( $"{deaths.Name} {deaths.GetInt()} times" );
Console.WriteLine( $"{deaths.Name} {deaths.GetFloat()} times" );
Assert.AreNotEqual( 0, deaths.GetInt() );
}
[TestMethod]
public async Task GetFriendStats()
{
var friend = new Friend( 76561197965732579 ); // Hezzy
// Download stats
var status = await friend.RequestUserStatsAsync();
Assert.AreNotEqual( false, status );
var deaths = friend.GetStatInt( "deaths" );
Console.WriteLine( $"Hezzy has died {deaths} times" );
Assert.AreNotEqual( 0, deaths );
var unlocked = friend.GetAchievement( "COLLECT_100_WOOD" );
Assert.AreNotEqual( false, unlocked );
var when = friend.GetAchievementUnlockTime( "COLLECT_100_WOOD" );
Assert.AreNotEqual( when, DateTime.MinValue );
Console.WriteLine( $"Hezzy unlocked COLLECT_100_WOOD {when}" );
}
[TestMethod]
public async Task GetStatGlobalInt()
{
var deaths = new Stat( "deaths" );
await deaths.GetGlobalIntDaysAsync( 5 );
var totalStartups = deaths.GetGlobalInt();
Assert.AreNotEqual( 0, totalStartups );
Console.WriteLine( $"Rust has had {totalStartups} deaths" );
}
[TestMethod]
public async Task GetStatGlobalHistoryInt()
{
var deaths = new Stat( "deaths" );
var history = await deaths.GetGlobalIntDaysAsync( 10 );
Assert.AreNotEqual( 0, history.Length );
for ( int i=0; i< history.Length; i++ )
{
Console.WriteLine( $"{i} : {history[i]}" );
}
}
}
}

@ -1,191 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class UserTest
{
[TestMethod]
public void GetVoice()
{
using ( var stream = new MemoryStream() )
{
int compressed = 0;
SteamUser.VoiceRecord = true;
var sw = Stopwatch.StartNew();
while ( sw.Elapsed.TotalSeconds < 3 )
{
System.Threading.Thread.Sleep( 10 );
compressed += SteamUser.ReadVoiceData( stream );
}
Assert.AreEqual( compressed, stream.Length );
Console.WriteLine( $"compressed: {compressed}", compressed );
Console.WriteLine( $"stream.Length: {stream.Length}", stream.Length );
}
}
[TestMethod]
public void OptimalSampleRate()
{
var rate = SteamUser.OptimalSampleRate;
Assert.AreNotEqual( rate, 0 );
Console.WriteLine( $"User.OptimalSampleRate: {SteamUser.OptimalSampleRate}" );
}
[TestMethod]
public void IsLoggedOn()
{
Assert.AreNotEqual( false, SteamClient.IsLoggedOn );
Console.WriteLine( $"User.IsLoggedOn: {SteamClient.IsLoggedOn}" );
}
[TestMethod]
public void SteamID()
{
Assert.AreNotEqual( 0, SteamClient.SteamId.Value );
Console.WriteLine( $"User.SteamID: {SteamClient.SteamId.Value}" );
}
[TestMethod]
public void AuthSession()
{
var ticket = SteamUser.GetAuthSessionTicket( SteamClient.SteamId );
Assert.AreNotEqual( 0, ticket.Handle );
Assert.AreNotEqual( 0, ticket.Data.Length );
Console.WriteLine( $"ticket.Handle: {ticket.Handle}" );
Console.WriteLine( $"ticket.Data: { string.Join( "", ticket.Data.Select( x => x.ToString( "x" ) ) ) }" );
var result = SteamUser.BeginAuthSession( ticket.Data, SteamClient.SteamId );
Console.WriteLine( $"result: { result }" );
Assert.AreEqual( result, BeginAuthResult.OK );
SteamUser.EndAuthSession( SteamClient.SteamId );
}
[TestMethod]
public async Task AuthSessionAsync()
{
var ticket = await SteamUser.GetAuthSessionTicketAsync( SteamClient.SteamId, 5.0 );
Assert.AreNotEqual( 0, ticket.Handle );
Assert.AreNotEqual( 0, ticket.Data.Length );
Console.WriteLine( $"ticket.Handle: {ticket.Handle}" );
Console.WriteLine( $"ticket.Data: { string.Join( "", ticket.Data.Select( x => x.ToString( "x" ) ) ) }" );
}
[TestMethod]
public async Task AuthTicketForWebApiAsync()
{
var ticket = await SteamUser.GetAuthTicketForWebApiAsync( "Test" );
Assert.AreNotEqual( 0, ticket.Handle );
Assert.AreNotEqual( 0, ticket.Data.Length );
Console.WriteLine( $"ticket.Handle: {ticket.Handle}" );
Console.WriteLine( $"ticket.Data: { string.Join( "", ticket.Data.Select( x => x.ToString( "x" ) ) ) }" );
}
[TestMethod]
public void SteamLevel()
{
Assert.AreNotEqual( 0, SteamUser.SteamLevel );
Console.WriteLine( $"User.SteamLevel: {SteamUser.SteamLevel}" );
}
[TestMethod]
public void Name()
{
Console.WriteLine( $"SteamClient.Name: {SteamClient.Name}" );
}
[TestMethod]
public async Task GetStoreAuthUrlAsync()
{
var rustskins = await SteamUser.GetStoreAuthUrlAsync( "https://store.steampowered.com/itemstore/252490/" );
Assert.IsNotNull( rustskins );
Console.WriteLine( $"rustskins: {rustskins}" );
}
[TestMethod]
public void IsPhoneVerified()
{
Console.WriteLine( $"User.IsPhoneVerified: {SteamUser.IsPhoneVerified}" );
}
[TestMethod]
public void IsTwoFactorEnabled()
{
Console.WriteLine( $"User.IsTwoFactorEnabled: {SteamUser.IsTwoFactorEnabled}" );
}
[TestMethod]
public void IsPhoneIdentifying()
{
Console.WriteLine( $"User.IsPhoneIdentifying: {SteamUser.IsPhoneIdentifying}" );
}
[TestMethod]
public void IsPhoneRequiringVerification()
{
Console.WriteLine( $"User.IsPhoneRequiringVerification: {SteamUser.IsPhoneRequiringVerification}" );
}
[TestMethod]
public async Task RequestEncryptedAppTicketAsyncWithData()
{
for ( int i=0; i<10; i++ )
{
var data = await SteamUser.RequestEncryptedAppTicketAsync( new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 } );
if ( data == null )
{
Console.WriteLine( $"Attempt {i}: Returned null.. waiting 1 seconds" );
await Task.Delay( 10000 );
continue;
}
Console.WriteLine( $"data: {BitConverter.ToString( data )}" );
return;
}
Assert.Fail();
}
[TestMethod]
public async Task RequestEncryptedAppTicketAsync()
{
for ( int i = 0; i < 6; i++ )
{
var data = await SteamUser.RequestEncryptedAppTicketAsync();
if ( data == null )
{
Console.WriteLine( $"Attempt {i}: Returned null.. waiting 1 seconds" );
await Task.Delay( 10000 );
continue;
}
Console.WriteLine( $"data: {BitConverter.ToString( data )}" );
return;
}
Assert.Fail();
}
}
}

@ -1,118 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Steamworks
{
[TestClass]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_api.dll" )]
public class UtilsTest
{
[TestMethod]
public void SecondsSinceAppActive()
{
var time = SteamUtils.SecondsSinceAppActive;
Console.WriteLine( $"{time}" );
}
[TestMethod]
public void SecondsSinceComputerActive()
{
var time = SteamUtils.SecondsSinceComputerActive;
Console.WriteLine( $"{time}" );
}
[TestMethod]
public void ConnectedUniverse()
{
var u = SteamUtils.ConnectedUniverse;
Console.WriteLine( $"{u}" );
}
[TestMethod]
public void SteamServerTime()
{
var time = SteamUtils.SteamServerTime;
Console.WriteLine( $"{time}" );
}
[TestMethod]
public void IpCountry()
{
var cnt = SteamUtils.IpCountry;
Console.WriteLine( $"{cnt}" );
}
[TestMethod]
public void UsingBatteryPower()
{
var cnt = SteamUtils.UsingBatteryPower;
Console.WriteLine( $"{cnt}" );
}
[TestMethod]
public void CurrentBatteryPower()
{
var cnt = SteamUtils.CurrentBatteryPower;
Console.WriteLine( $"{cnt}" );
}
[TestMethod]
public void AppId()
{
var cnt = SteamClient.AppId;
Assert.IsTrue( cnt.Value > 0 );
Console.WriteLine( $"{cnt.Value}" );
}
[TestMethod]
public void IsOverlayEnabled()
{
var cnt = SteamUtils.IsOverlayEnabled;
Console.WriteLine( $"{cnt}" );
}
[TestMethod]
public async Task CheckFileSignature()
{
var sig = await SteamUtils.CheckFileSignatureAsync( "hl2.exe" );
Console.WriteLine( $"{sig}" );
}
[TestMethod]
public void SteamUILanguage()
{
var cnt = SteamUtils.SteamUILanguage;
Console.WriteLine( $"{cnt}" );
}
[TestMethod]
public void IsSteamRunningInVR()
{
var cnt = SteamUtils.IsSteamRunningInVR;
Console.WriteLine( $"{cnt}" );
}
[TestMethod]
public void IsSteamInBigPictureMode()
{
var cnt = SteamUtils.IsSteamInBigPictureMode;
Console.WriteLine( $"{cnt}" );
}
[TestMethod]
public void VrHeadsetStreaming()
{
var cnt = SteamUtils.VrHeadsetStreaming;
Console.WriteLine( $"{cnt}" );
}
}
}

@ -1,74 +0,0 @@
"In Game Actions"
{
"actions"
{
"InGameControls"
{
"title" "#Set_Ingame"
"StickPadGyro"
{
"Move"
{
"title" "#Action_Move"
"input_mode" "joystick_move"
}
"Camera"
{
"title" "#Action_Camera"
"input_mode" "absolute_mouse"
}
}
"AnalogTrigger"
{
"Throttle" "#Action_Throttle"
}
"Button"
{
"fire" "#Action_Fire"
"Jump" "#Action_Jump"
"pause_menu" "#Action_Menu"
}
}
"MenuControls"
{
"title" "#Set_Menu"
"StickPadGyro"
{
}
"AnalogTrigger"
{
}
"Button"
{
"menu_up" "#Menu_Up"
"menu_down" "#Menu_Down"
"menu_left" "#Menu_Left"
"menu_right" "#Menu_Right"
"menu_select" "#Menu_Select"
"menu_cancel" "#Menu_Cancel"
"pause_menu" "#Action_ReturnToGame"
}
}
}
"localization"
{
"english"
{
"Set_Ingame" "In-Game Controls"
"Set_Menu" "Menu Controls"
"Action_Move" "Movement"
"Action_Camera" "Camera"
"Action_Throttle" "Throttle"
"Action_Fire" "Fire Weapon"
"Action_Jump" "Jump"
"Action_Menu" "Pause Menu"
"Action_ReturnToGame" "Return To Game"
"Menu_Up" "Up"
"Menu_Down" "Down"
"Menu_Left" "Left"
"Menu_Right" "Right"
"Menu_Select" "Select"
"Menu_Cancel" "Cancel"
}
}
}

Binary file not shown.

@ -0,0 +1 @@
252490

@ -1,74 +0,0 @@
"In Game Actions"
{
"actions"
{
"InGameControls"
{
"title" "#Set_Ingame"
"StickPadGyro"
{
"Move"
{
"title" "#Action_Move"
"input_mode" "joystick_move"
}
"Camera"
{
"title" "#Action_Camera"
"input_mode" "absolute_mouse"
}
}
"AnalogTrigger"
{
"Throttle" "#Action_Throttle"
}
"Button"
{
"fire" "#Action_Fire"
"Jump" "#Action_Jump"
"pause_menu" "#Action_Menu"
}
}
"MenuControls"
{
"title" "#Set_Menu"
"StickPadGyro"
{
}
"AnalogTrigger"
{
}
"Button"
{
"menu_up" "#Menu_Up"
"menu_down" "#Menu_Down"
"menu_left" "#Menu_Left"
"menu_right" "#Menu_Right"
"menu_select" "#Menu_Select"
"menu_cancel" "#Menu_Cancel"
"pause_menu" "#Action_ReturnToGame"
}
}
}
"localization"
{
"english"
{
"Set_Ingame" "In-Game Controls"
"Set_Menu" "Menu Controls"
"Action_Move" "Movement"
"Action_Camera" "Camera"
"Action_Throttle" "Throttle"
"Action_Fire" "Fire Weapon"
"Action_Jump" "Jump"
"Action_Menu" "Pause Menu"
"Action_ReturnToGame" "Return To Game"
"Menu_Up" "Up"
"Menu_Down" "Down"
"Menu_Left" "Left"
"Menu_Right" "Right"
"Menu_Select" "Select"
"Menu_Cancel" "Cancel"
}
}
}

@ -0,0 +1 @@
252490

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MSTest.TestAdapter" version="2.0.0-beta4" targetFramework="net46" />
<package id="MSTest.TestFramework" version="2.0.0-beta4" targetFramework="net46" />
<package id="Newtonsoft.Json" version="9.0.2-beta1" targetFramework="net452" />
</packages>

Binary file not shown.

Binary file not shown.

@ -1,107 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29009.5
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generator", "Generator\Generator.csproj", "{B7225D11-2AAA-49D6-AE93-A73696EA35FE}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facepunch.Steamworks", "Facepunch.Steamworks\Facepunch.Steamworks.csproj", "{DC2D9FA9-F005-468F-8581-85C79F4E0034}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Win64", "Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj", "{8C73DA93-73AD-4445-9A2C-15D4A44337D3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Win32", "Facepunch.Steamworks\Facepunch.Steamworks.Win32.csproj", "{2D6247F6-8AB2-405F-A00E-3A364B808A55}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Posix", "Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj", "{C62FF421-BE44-4DB0-B99A-E13E007A30B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facepunch.Steamworks.TestWin32", "Facepunch.Steamworks.Test\Facepunch.Steamworks.TestWin32.csproj", "{3F6183AD-D966-44F2-A6EB-42E61E591B49}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facepunch.Steamworks.TestWin64", "Facepunch.Steamworks.Test\Facepunch.Steamworks.TestWin64.csproj", "{165081E3-BD96-404B-B83E-A635F1AF7CDE}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Facepunch.Steamworks.Test", "Facepunch.Steamworks.Test\Facepunch.Steamworks.Test.csproj", "{3F6183AD-D966-44F2-A6EB-42E61E591B49}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Debug|x64.ActiveCfg = Debug|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Debug|x64.Build.0 = Debug|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Debug|x86.ActiveCfg = Debug|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Debug|x86.Build.0 = Debug|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Release|Any CPU.Build.0 = Release|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Release|x64.ActiveCfg = Release|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Release|x64.Build.0 = Release|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Release|x86.ActiveCfg = Release|Any CPU
{B7225D11-2AAA-49D6-AE93-A73696EA35FE}.Release|x86.Build.0 = Release|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Debug|x64.ActiveCfg = Debug|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Debug|x64.Build.0 = Debug|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Debug|x86.ActiveCfg = Debug|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Debug|x86.Build.0 = Debug|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Release|Any CPU.Build.0 = Release|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Release|x64.ActiveCfg = Release|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Release|x64.Build.0 = Release|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Release|x86.ActiveCfg = Release|Any CPU
{8C73DA93-73AD-4445-9A2C-15D4A44337D3}.Release|x86.Build.0 = Release|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Debug|x64.ActiveCfg = Debug|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Debug|x64.Build.0 = Debug|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Debug|x86.ActiveCfg = Debug|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Debug|x86.Build.0 = Debug|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Release|Any CPU.Build.0 = Release|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Release|x64.ActiveCfg = Release|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Release|x64.Build.0 = Release|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Release|x86.ActiveCfg = Release|Any CPU
{2D6247F6-8AB2-405F-A00E-3A364B808A55}.Release|x86.Build.0 = Release|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Debug|x64.ActiveCfg = Debug|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Debug|x64.Build.0 = Debug|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Debug|x86.ActiveCfg = Debug|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Debug|x86.Build.0 = Debug|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Release|Any CPU.Build.0 = Release|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Release|x64.ActiveCfg = Release|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Release|x64.Build.0 = Release|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Release|x86.ActiveCfg = Release|Any CPU
{C62FF421-BE44-4DB0-B99A-E13E007A30B9}.Release|x86.Build.0 = Release|Any CPU
{DC2D9FA9-F005-468F-8581-85C79F4E0034}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC2D9FA9-F005-468F-8581-85C79F4E0034}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC2D9FA9-F005-468F-8581-85C79F4E0034}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC2D9FA9-F005-468F-8581-85C79F4E0034}.Release|Any CPU.Build.0 = Release|Any CPU
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Debug|x64.ActiveCfg = Debug|x64
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Debug|x64.Build.0 = Debug|x64
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Debug|x86.ActiveCfg = Debug|x86
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Debug|x86.Build.0 = Debug|x86
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Release|Any CPU.Build.0 = Release|Any CPU
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Release|x64.ActiveCfg = Release|x64
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Release|x64.Build.0 = Release|x64
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Release|x86.ActiveCfg = Release|x86
{3F6183AD-D966-44F2-A6EB-42E61E591B49}.Release|x86.Build.0 = Release|x86
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Debug|x64.ActiveCfg = Debug|x64
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Debug|x64.Build.0 = Debug|x64
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Debug|x86.ActiveCfg = Debug|x86
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Debug|x86.Build.0 = Debug|x86
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Release|Any CPU.Build.0 = Release|Any CPU
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Release|x64.ActiveCfg = Release|x64
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Release|x64.Build.0 = Release|x64
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Release|x86.ActiveCfg = Release|x86
{165081E3-BD96-404B-B83E-A635F1AF7CDE}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {506FC2EC-38D1-45E2-BAE8-D61584162F7D}
EndGlobalSection
EndGlobal

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Facepunch.Steamworks.Interop;
namespace Facepunch.Steamworks
{
public class BaseSteamworks : IDisposable
{
/// <summary>
/// Current running program's AppId
/// </summary>
public uint AppId { get; internal set; }
public Networking Networking { get; internal set; }
public Inventory Inventory { get; internal set; }
public Workshop Workshop { get; internal set; }
public virtual void Dispose()
{
foreach ( var d in Disposables )
{
d.Dispose();
}
Disposables.Clear();
Workshop.Dispose();
Workshop = null;
Inventory.Dispose();
Inventory = null;
Networking.Dispose();
Networking = null;
if ( native != null )
{
native.Dispose();
native = null;
}
}
public void SetupCommonInterfaces()
{
Networking = new Steamworks.Networking( this, native.networking );
Inventory = new Steamworks.Inventory( native.inventory, IsGameServer );
Workshop = new Steamworks.Workshop( this, native.ugc, native.remoteStorage );
}
public bool IsValid
{
get { return native != null; }
}
internal Interop.NativeInterface native;
internal virtual bool IsGameServer { get { return false; } }
private List<IDisposable> Disposables = new List<IDisposable>();
public enum MessageType : int
{
Message = 0,
Warning = 1
}
/// <summary>
/// Called with a message from Steam
/// </summary>
public Action<MessageType, string> OnMessage;
/// <summary>
/// Global callback type
/// </summary>
internal void AddCallback<T, TSmall>( Action<T> Callback, int id )
{
var callback = new Callback<T, TSmall>( IsGameServer, id, Callback );
Disposables.Add( callback );
}
internal void AddCallback<T>( Action<T> Callback, int id )
{
AddCallback<T, T>( Callback, id );
}
public Action OnUpdate;
public virtual void Update()
{
RunCallbackQueue();
Inventory.Update();
Networking.Update();
if ( OnUpdate != null )
OnUpdate();
}
List<CallResult> Callbacks = new List<CallResult>();
/// <summary>
/// Call results are results to specific actions
/// </summary>
internal void AddCallResult( CallResult call )
{
if ( call == null ) throw new ArgumentNullException( "call" );
if ( FinishCallback( call ) )
return;
Callbacks.Add( call );
}
void RunCallbackQueue()
{
for ( int i=0; i< Callbacks.Count(); i++ )
{
if ( !FinishCallback( Callbacks[i] ) )
continue;
Callbacks.RemoveAt( i );
i--;
}
}
bool FinishCallback( CallResult call )
{
bool failed = true;
if ( !native.utils.IsAPICallCompleted( call.Handle, ref failed ) )
return false;
if ( failed )
{
//
// TODO - failure reason?
//
return true;
}
call.Run( native.utils );
return true;
}
}
}

@ -1,98 +0,0 @@
using Steamworks.Data;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Steamworks
{
/// <summary>
/// An awaitable version of a SteamAPICall_t
/// </summary>
internal struct CallResult<T> : INotifyCompletion where T : struct, ICallbackData
{
SteamAPICall_t call;
ISteamUtils utils;
bool server;
public CallResult( SteamAPICall_t call, bool server )
{
this.call = call;
this.server = server;
utils = (server ? SteamUtils.InterfaceServer : SteamUtils.InterfaceClient) as ISteamUtils;
if ( utils == null )
utils = SteamUtils.Interface as ISteamUtils;
}
/// <summary>
/// This gets called if IsComplete returned false on the first call.
/// The Action "continues" the async call. We pass it to the Dispatch
/// to be called when the callback returns.
/// </summary>
public void OnCompleted( Action continuation )
{
if (IsCompleted)
continuation();
else
Dispatch.OnCallComplete<T>(call, continuation, server);
}
/// <summary>
/// Gets the result. This is called internally by the async shit.
/// </summary>
public T? GetResult()
{
bool failed = false;
if ( !utils.IsAPICallCompleted( call, ref failed ) || failed )
return null;
var t = default( T );
var size = t.DataSize;
var ptr = Marshal.AllocHGlobal( size );
try
{
if ( !utils.GetAPICallResult( call, ptr, size, (int)t.CallbackType, ref failed ) || failed )
{
Dispatch.OnDebugCallback?.Invoke( t.CallbackType, "!GetAPICallResult or failed", server );
return null;
}
Dispatch.OnDebugCallback?.Invoke( t.CallbackType, Dispatch.CallbackToString( t.CallbackType, ptr, size ), server );
return ((T)Marshal.PtrToStructure( ptr, typeof( T ) ));
}
finally
{
Marshal.FreeHGlobal( ptr );
}
}
/// <summary>
/// Return true if complete or failed
/// </summary>
public bool IsCompleted
{
get
{
bool failed = false;
if ( utils.IsAPICallCompleted( call, ref failed ) || failed )
return true;
return false;
}
}
/// <summary>
/// This is what makes this struct awaitable
/// </summary>
internal CallResult<T> GetAwaiter()
{
return this;
}
}
}

@ -1,17 +0,0 @@
using Steamworks.Data;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Steamworks
{
/// <summary>
/// Gives us a generic way to get the CallbackId of structs
/// </summary>
internal interface ICallbackData
{
CallbackType CallbackType { get; }
int DataSize { get; }
}
}

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Facepunch.Steamworks.Callbacks
{
internal static class Index
{
internal const int User = 100;
internal const int Networking = 1200;
internal const int RemoteStorage = 1300;
internal const int UGC = 3400;
}
public enum Result : int
{
OK = 1, // success
Fail = 2, // generic failure
NoConnection = 3, // no/failed network connection
// k_EResultNoConnectionRetry = 4, // OBSOLETE - removed
InvalidPassword = 5, // password/ticket is invalid
LoggedInElsewhere = 6, // same user logged in elsewhere
InvalidProtocolVer = 7, // protocol version is incorrect
InvalidParam = 8, // a parameter is incorrect
FileNotFound = 9, // file was not found
Busy = 10, // called method busy - action not taken
InvalidState = 11, // called object was in an invalid state
InvalidName = 12, // name is invalid
InvalidEmail = 13, // email is invalid
DuplicateName = 14, // name is not unique
AccessDenied = 15, // access is denied
Timeout = 16, // operation timed out
Banned = 17, // VAC2 banned
AccountNotFound = 18, // account not found
InvalidSteamID = 19, // steamID is invalid
ServiceUnavailable = 20, // The requested service is currently unavailable
NotLoggedOn = 21, // The user is not logged on
Pending = 22, // Request is pending (may be in process, or waiting on third party)
EncryptionFailure = 23, // Encryption or Decryption failed
InsufficientPrivilege = 24, // Insufficient privilege
LimitExceeded = 25, // Too much of a good thing
Revoked = 26, // Access has been revoked (used for revoked guest passes)
Expired = 27, // License/Guest pass the user is trying to access is expired
AlreadyRedeemed = 28, // Guest pass has already been redeemed by account, cannot be acked again
DuplicateRequest = 29, // The request is a duplicate and the action has already occurred in the past, ignored this time
AlreadyOwned = 30, // All the games in this guest pass redemption request are already owned by the user
IPNotFound = 31, // IP address not found
PersistFailed = 32, // failed to write change to the data store
LockingFailed = 33, // failed to acquire access lock for this operation
LogonSessionReplaced = 34,
ConnectFailed = 35,
HandshakeFailed = 36,
IOFailure = 37,
RemoteDisconnect = 38,
ShoppingCartNotFound = 39, // failed to find the shopping cart requested
Blocked = 40, // a user didn't allow it
Ignored = 41, // target is ignoring sender
NoMatch = 42, // nothing matching the request found
AccountDisabled = 43,
ServiceReadOnly = 44, // this service is not accepting content changes right now
AccountNotFeatured = 45, // account doesn't have value, so this feature isn't available
AdministratorOK = 46, // allowed to take this action, but only because requester is admin
ContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol.
TryAnotherCM = 48, // The current CM can't service the user making a request, user should try another.
PasswordRequiredToKickSession = 49,// You are already logged in elsewhere, this cached credential login has failed.
AlreadyLoggedInElsewhere = 50, // You are already logged in elsewhere, you must wait
Suspended = 51, // Long running operation (content download) suspended/paused
Cancelled = 52, // Operation canceled (typically by user: content download)
DataCorruption = 53, // Operation canceled because data is ill formed or unrecoverable
DiskFull = 54, // Operation canceled - not enough disk space.
RemoteCallFailed = 55, // an remote call or IPC call failed
PasswordUnset = 56, // Password could not be verified as it's unset server side
ExternalAccountUnlinked = 57, // External account (PSN, Facebook...) is not linked to a Steam account
PSNTicketInvalid = 58, // PSN ticket was invalid
ExternalAccountAlreadyLinked = 59, // External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first
RemoteFileConflict = 60, // The sync cannot resume due to a conflict between the local and remote files
IllegalPassword = 61, // The requested new password is not legal
SameAsPreviousValue = 62, // new value is the same as the old one ( secret question and answer )
AccountLogonDenied = 63, // account login denied due to 2nd factor authentication failure
CannotUseOldPassword = 64, // The requested new password is not legal
InvalidLoginAuthCode = 65, // account login denied due to auth code invalid
AccountLogonDeniedNoMail = 66, // account login denied due to 2nd factor auth failure - and no mail has been sent
HardwareNotCapableOfIPT = 67, //
IPTInitError = 68, //
ParentalControlRestricted = 69, // operation failed due to parental control restrictions for current user
FacebookQueryError = 70, // Facebook query returned an error
ExpiredLoginAuthCode = 71, // account login denied due to auth code expired
IPLoginRestrictionFailed = 72,
AccountLockedDown = 73,
AccountLogonDeniedVerifiedEmailRequired = 74,
NoMatchingURL = 75,
BadResponse = 76, // parse failure, missing field, etc.
RequirePasswordReEntry = 77, // The user cannot complete the action until they re-enter their password
ValueOutOfRange = 78, // the value entered is outside the acceptable range
UnexpectedError = 79, // something happened that we didn't expect to ever happen
Disabled = 80, // The requested service has been configured to be unavailable
InvalidCEGSubmission = 81, // The set of files submitted to the CEG server are not valid !
RestrictedDevice = 82, // The device being used is not allowed to perform this action
RegionLocked = 83, // The action could not be complete because it is region restricted
RateLimitExceeded = 84, // Temporary rate limit exceeded, try again later, different from k_EResultLimitExceeded which may be permanent
AccountLoginDeniedNeedTwoFactor = 85, // Need two-factor code to login
ItemDeleted = 86, // The thing we're trying to access has been deleted
AccountLoginDeniedThrottle = 87, // login attempt failed, try to throttle response to possible attacker
TwoFactorCodeMismatch = 88, // two factor code mismatch
TwoFactorActivationCodeMismatch = 89, // activation code for two-factor didn't match
AccountAssociatedToMultiplePartners = 90, // account has been associated with multiple partners
NotModified = 91, // data not modified
NoMobileDevice = 92, // the account does not have a mobile device associated with it
TimeNotSynced = 93, // the time presented is out of range or tolerance
SmsCodeFailed = 94, // SMS code failure (no match, none pending, etc.)
AccountLimitExceeded = 95, // Too many accounts access this resource
AccountActivityLimitExceeded = 96, // Too many changes to this account
PhoneActivityLimitExceeded = 97, // Too many changes to this phone
RefundToWallet = 98, // Cannot refund to payment method, must use wallet
EmailSendFailure = 99, // Cannot send an email
NotSettled = 100, // Can't perform operation till payment has settled
NeedCaptcha = 101, // Needs to provide a valid captcha
GSLTDenied = 102, // a game server login token owned by this token's owner has been banned
GSOwnerDenied = 103, // game server owner is denied for other reason (account lock, community ban, vac ban, missing phone)
InvalidItemType = 104, // the type of thing we were requested to act on is invalid
IPBanned = 105, // the ip address has been banned from taking this action
};
}

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Facepunch.Steamworks.Callbacks.Networking
{
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
internal class P2PSessionRequest
{
public ulong SteamID;
public const int CallbackId = Index.Networking + 2;
};
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
internal class P2PSessionConnectFail
{
public ulong SteamID;
public Steamworks.Networking.SessionError Error;
public const int CallbackId = Index.Networking + 3;
};
}

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Facepunch.Steamworks.Callbacks.User
{
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
internal struct ValidateAuthTicketResponse
{
public ulong SteamID;
public int AuthResponse;
public ulong OwnerSteamID;
public const int CallbackId = Index.User + 43;
public enum Response : int
{
Okay = 0, // Steam has verified the user is online, the ticket is valid and ticket has not been reused.
UserNotConnectedToSteam = 1, // The user in question is not connected to steam
NoLicenseOrExpired = 2, // The license has expired.
VACBanned = 3, // The user is VAC banned for this game.
LoggedInElseWhere = 4, // The user account has logged in elsewhere and the session containing the game instance has been disconnected.
VACCheckTimedOut = 5, // VAC has been unable to perform anti-cheat checks on this user
AuthTicketCanceled = 6, // The ticket has been canceled by the issuer
AuthTicketInvalidAlreadyUsed = 7, // This ticket has already been used, it is not valid.
AuthTicketInvalid = 8, // This ticket is not from a user instance currently connected to steam.
PublisherIssuedBan = 9, // The user is banned for this game. The ban came via the web api and not VAC
};
};
}

@ -0,0 +1,107 @@
using System.Runtime.InteropServices;
using Facepunch.Steamworks.Interop;
namespace Facepunch.Steamworks.Callbacks.Workshop
{
[StructLayout( LayoutKind.Sequential, Pack = 8 )]
internal struct ItemInstalled
{
public uint AppId;
public ulong FileId;
public const int CallbackId = Index.UGC + 5;
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
internal struct Small
{
public uint AppId;
public ulong FileId;
};
};
[StructLayout( LayoutKind.Sequential, Pack = 8 )]
internal struct DownloadResult
{
public uint AppId;
public ulong FileId;
public Result Result;
public const int CallbackId = Index.UGC + 6;
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
internal struct Small
{
public uint AppId;
public ulong FileId;
public Result Result;
};
};
internal class QueryCompleted : CallResult<QueryCompleted.Data, QueryCompleted.Data.Small>
{
public override int CallbackId { get { return Index.UGC + 1; } }
[StructLayout( LayoutKind.Sequential, Pack = 8 )]
internal struct Data
{
internal ulong Handle;
internal int Result;
internal uint NumResultsReturned;
internal uint TotalMatchingResults;
internal bool CachedData;
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
internal struct Small
{
internal ulong Handle;
internal int Result;
internal uint NumResultsReturned;
internal uint TotalMatchingResults;
internal bool CachedData;
};
};
}
internal class CreateItem : CallResult<CreateItem.Data, CreateItem.Data.Small>
{
public override int CallbackId { get { return Index.UGC + 3; } }
[StructLayout( LayoutKind.Sequential, Pack = 8 )]
internal struct Data
{
internal Result Result;
internal ulong FileId;
internal bool NeedsLegalAgreement;
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
internal struct Small
{
internal Result Result;
internal ulong FileId;
internal bool NeedsLegalAgreement;
};
};
}
internal class SubmitItemUpdate : CallResult<SubmitItemUpdate.Data, SubmitItemUpdate.Data.Small>
{
public override int CallbackId { get { return Index.UGC + 4; } }
[StructLayout( LayoutKind.Sequential, Pack = 8 )]
internal struct Data
{
internal Result Result;
internal bool NeedsLegalAgreement;
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
internal struct Small
{
internal Result Result;
internal bool NeedsLegalAgreement;
};
};
}
}

@ -1,30 +0,0 @@
using System;
namespace Steamworks
{
public class AuthTicket : IDisposable
{
public byte[] Data;
public uint Handle;
/// <summary>
/// Cancels a ticket.
/// You should cancel your ticket when you close the game or leave a server.
/// </summary>
public void Cancel()
{
if ( Handle != 0 )
{
SteamUser.Internal.CancelAuthTicket( Handle );
}
Handle = 0;
Data = null;
}
public void Dispose()
{
Cancel();
}
}
}

@ -1,334 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Steamworks.Data;
using Steamworks;
using System.Linq;
namespace Steamworks
{
/// <summary>
/// Responsible for all callback/callresult handling
///
/// This manually pumps Steam's message queue and dispatches those
/// events to any waiting callbacks/callresults.
/// </summary>
public static class Dispatch
{
/// <summary>
/// If set then we'll call this function every time a callback is generated.
///
/// This is SLOW!! - it's for debugging - don't keep it on all the time. If you want to access a specific
/// callback then please create an issue on github and I'll add it!
///
/// Params are : [Callback Type] [Callback Contents] [server]
///
/// </summary>
public static Action<CallbackType, string, bool> OnDebugCallback;
/// <summary>
/// Called if an exception happens during a callback/callresult.
/// This is needed because the exception isn't always accessible when running
/// async.. and can fail silently. With this hooked you won't be stuck wondering
/// what happened.
/// </summary>
public static Action<Exception> OnException;
#region interop
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ManualDispatch_Init", CallingConvention = CallingConvention.Cdecl )]
internal static extern void SteamAPI_ManualDispatch_Init();
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ManualDispatch_RunFrame", CallingConvention = CallingConvention.Cdecl )]
internal static extern void SteamAPI_ManualDispatch_RunFrame( HSteamPipe pipe );
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ManualDispatch_GetNextCallback", CallingConvention = CallingConvention.Cdecl )]
[return: MarshalAs( UnmanagedType.I1 )]
internal static extern bool SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe pipe, [In, Out] ref CallbackMsg_t msg );
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ManualDispatch_FreeLastCallback", CallingConvention = CallingConvention.Cdecl )]
[return: MarshalAs( UnmanagedType.I1 )]
internal static extern bool SteamAPI_ManualDispatch_FreeLastCallback( HSteamPipe pipe );
[StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )]
internal struct CallbackMsg_t
{
public HSteamUser m_hSteamUser; // Specific user to whom this callback applies.
public CallbackType Type; // Callback identifier. (Corresponds to the k_iCallback enum in the callback structure.)
public IntPtr Data; // Points to the callback structure
public int DataSize; // Size of the data pointed to by m_pubParam
};
#endregion
internal static HSteamPipe ClientPipe { get; set; }
internal static HSteamPipe ServerPipe { get; set; }
/// <summary>
/// This gets called from Client/Server Init
/// It's important to switch to the manual dispatcher
/// </summary>
internal static void Init()
{
SteamAPI_ManualDispatch_Init();
}
/// <summary>
/// Make sure we don't call Frame in a callback - because that'll cause some issues for everyone.
/// </summary>
static bool runningFrame = false;
/// <summary>
/// Calls RunFrame and processes events from this Steam Pipe
/// </summary>
internal static void Frame( HSteamPipe pipe )
{
if ( runningFrame )
return;
try
{
runningFrame = true;
SteamAPI_ManualDispatch_RunFrame( pipe );
SteamNetworkingUtils.OutputDebugMessages();
CallbackMsg_t msg = default;
while ( SteamAPI_ManualDispatch_GetNextCallback( pipe, ref msg ) )
{
try
{
ProcessCallback( msg, pipe == ServerPipe );
}
finally
{
SteamAPI_ManualDispatch_FreeLastCallback( pipe );
}
}
}
catch ( System.Exception e )
{
OnException?.Invoke( e );
}
finally
{
runningFrame = false;
}
}
/// <summary>
/// To be safe we don't call the continuation functions while iterating
/// the Callback list. This is maybe overly safe because the only way this
/// could be an issue is if the callback list is modified in the continuation
/// which would only happen if starting or shutting down in the callback.
/// </summary>
static List<Action<IntPtr>> actionsToCall = new List<Action<IntPtr>>();
/// <summary>
/// A callback is a general global message
/// </summary>
private static void ProcessCallback( CallbackMsg_t msg, bool isServer )
{
OnDebugCallback?.Invoke( msg.Type, CallbackToString( msg.Type, msg.Data, msg.DataSize ), isServer );
// Is this a special callback telling us that the call result is ready?
if ( msg.Type == CallbackType.SteamAPICallCompleted )
{
ProcessResult( msg );
return;
}
if ( Callbacks.TryGetValue( msg.Type, out var list ) )
{
actionsToCall.Clear();
foreach ( var item in list )
{
if ( item.server != isServer )
continue;
actionsToCall.Add( item.action );
}
foreach ( var action in actionsToCall )
{
action( msg.Data );
}
actionsToCall.Clear();
}
}
/// <summary>
/// Given a callback, try to turn it into a string
/// </summary>
internal static string CallbackToString( CallbackType type, IntPtr data, int expectedsize )
{
if ( !CallbackTypeFactory.All.TryGetValue( type, out var t ) )
return $"[{type} not in sdk]";
var strct = data.ToType( t );
if ( strct == null )
return "[null]";
var str = "";
var fields = t.GetFields( System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic );
if ( fields.Length == 0 )
return "[no fields]";
var columnSize = fields.Max( x => x.Name.Length ) + 1;
if ( columnSize < 10 )
columnSize = 10;
foreach ( var field in fields )
{
var spaces = (columnSize - field.Name.Length);
if ( spaces < 0 ) spaces = 0;
str += $"{new String( ' ', spaces )}{field.Name}: {field.GetValue( strct )}\n";
}
return str.Trim( '\n' );
}
/// <summary>
/// A result is a reply to a specific command
/// </summary>
private static void ProcessResult( CallbackMsg_t msg )
{
var result = msg.Data.ToType<SteamAPICallCompleted_t>();
//
// Do we have an entry added via OnCallComplete
//
if ( !ResultCallbacks.TryGetValue( result.AsyncCall, out var callbackInfo ) )
{
//
// This can happen if the callback result was immediately available
// so we just returned that without actually going through the callback
// dance. It's okay for this to fail.
//
//
// But still let everyone know that this happened..
//
OnDebugCallback?.Invoke( (CallbackType)result.Callback, $"[no callback waiting/required]", false );
return;
}
// Remove it before we do anything, incase the continuation throws exceptions
ResultCallbacks.Remove( result.AsyncCall );
// At this point whatever async routine called this
// continues running.
callbackInfo.continuation();
}
/// <summary>
/// Pumps the queue in an async loop so we don't
/// have to think about it. This has the advantage that
/// you can call .Wait() on async shit and it still works.
/// </summary>
internal static async void LoopClientAsync()
{
while ( ClientPipe != 0 )
{
Frame( ClientPipe );
await Task.Delay( 16 );
}
}
/// <summary>
/// Pumps the queue in an async loop so we don't
/// have to think about it. This has the advantage that
/// you can call .Wait() on async shit and it still works.
/// </summary>
internal static async void LoopServerAsync()
{
while ( ServerPipe != 0 )
{
Frame( ServerPipe );
await Task.Delay( 32 );
}
}
struct ResultCallback
{
public Action continuation;
public bool server;
}
static Dictionary<ulong, ResultCallback> ResultCallbacks = new Dictionary<ulong, ResultCallback>();
/// <summary>
/// Watch for a steam api call
/// </summary>
internal static void OnCallComplete<T>( SteamAPICall_t call, Action continuation, bool server ) where T : struct, ICallbackData
{
ResultCallbacks[call.Value] = new ResultCallback
{
continuation = continuation,
server = server
};
}
struct Callback
{
public Action<IntPtr> action;
public bool server;
}
static Dictionary<CallbackType, List<Callback>> Callbacks = new Dictionary<CallbackType, List<Callback>>();
/// <summary>
/// Install a global callback. The passed function will get called if it's all good.
/// </summary>
internal static void Install<T>( Action<T> p, bool server = false ) where T : ICallbackData
{
var t = default( T );
var type = t.CallbackType;
if ( !Callbacks.TryGetValue( type, out var list ) )
{
list = new List<Callback>();
Callbacks[type] = list;
}
list.Add( new Callback
{
action = x => p( x.ToType<T>() ),
server = server
} );
}
internal static void ShutdownServer()
{
ServerPipe = 0;
foreach ( var callback in Callbacks )
{
Callbacks[callback.Key].RemoveAll( x => x.server );
}
ResultCallbacks = ResultCallbacks.Where( x => !x.Value.server )
.ToDictionary( x => x.Key, x => x.Value );
}
internal static void ShutdownClient()
{
ClientPipe = 0;
foreach ( var callback in Callbacks )
{
Callbacks[callback.Key].RemoveAll( x => !x.server );
}
ResultCallbacks = ResultCallbacks.Where( x => x.Value.server )
.ToDictionary( x => x.Key, x => x.Value );
}
}
}

@ -1,54 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Steamworks.Data;
namespace Steamworks
{
internal static class SteamAPI
{
internal static class Native
{
[DllImport( Platform.LibraryName, EntryPoint = "SteamInternal_SteamAPI_Init", CallingConvention = CallingConvention.Cdecl )]
public static extern SteamAPIInitResult SteamInternal_SteamAPI_Init( IntPtr pszInternalCheckInterfaceVersions, IntPtr pOutErrMsg );
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_Shutdown", CallingConvention = CallingConvention.Cdecl )]
public static extern void SteamAPI_Shutdown();
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_GetHSteamPipe", CallingConvention = CallingConvention.Cdecl )]
public static extern HSteamPipe SteamAPI_GetHSteamPipe();
[DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_RestartAppIfNecessary", CallingConvention = CallingConvention.Cdecl )]
[return: MarshalAs( UnmanagedType.I1 )]
public static extern bool SteamAPI_RestartAppIfNecessary( uint unOwnAppID );
}
static internal SteamAPIInitResult Init( string pszInternalCheckInterfaceVersions, out string pOutErrMsg )
{
using var interfaceVersionsStr = new Utf8StringToNative( pszInternalCheckInterfaceVersions );
using var buffer = Helpers.Memory.Take();
var result = Native.SteamInternal_SteamAPI_Init( interfaceVersionsStr.Pointer, buffer.Ptr );
pOutErrMsg = Helpers.MemoryToString( buffer.Ptr );
return result;
}
static internal void Shutdown()
{
Native.SteamAPI_Shutdown();
}
static internal HSteamPipe GetHSteamPipe()
{
return Native.SteamAPI_GetHSteamPipe();
}
static internal bool RestartAppIfNecessary( uint unOwnAppID )
{
return Native.SteamAPI_RestartAppIfNecessary( unOwnAppID );
}
}
}

@ -1,40 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Steamworks.Data;
namespace Steamworks
{
internal static class SteamGameServer
{
internal static class Native
{
[DllImport( Platform.LibraryName, EntryPoint = "SteamGameServer_RunCallbacks", CallingConvention = CallingConvention.Cdecl )]
public static extern void SteamGameServer_RunCallbacks();
[DllImport( Platform.LibraryName, EntryPoint = "SteamGameServer_Shutdown", CallingConvention = CallingConvention.Cdecl )]
public static extern void SteamGameServer_Shutdown();
[DllImport( Platform.LibraryName, EntryPoint = "SteamGameServer_GetHSteamPipe", CallingConvention = CallingConvention.Cdecl )]
public static extern HSteamPipe SteamGameServer_GetHSteamPipe();
}
static internal void RunCallbacks()
{
Native.SteamGameServer_RunCallbacks();
}
static internal void Shutdown()
{
Native.SteamGameServer_Shutdown();
}
static internal HSteamPipe GetHSteamPipe()
{
return Native.SteamGameServer_GetHSteamPipe();
}
}
}

@ -1,28 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Steamworks.Data;
namespace Steamworks
{
internal static class SteamInternal
{
internal static class Native
{
[DllImport( Platform.LibraryName, EntryPoint = "SteamInternal_GameServer_Init_V2", CallingConvention = CallingConvention.Cdecl )]
public static extern SteamAPIInitResult SteamInternal_GameServer_Init_V2( uint unIP, ushort usGamePort, ushort usQueryPort, int eServerMode, IntPtr pchVersionString, IntPtr pszInternalCheckInterfaceVersions, IntPtr pOutErrMsg );
}
static internal SteamAPIInitResult GameServer_Init( uint unIP, ushort usGamePort, ushort usQueryPort, int eServerMode, string pchVersionString, string pszInternalCheckInterfaceVersions, out string pOutErrMsg )
{
using var versionStr = new Utf8StringToNative( pchVersionString );
using var interfaceVersionsStr = new Utf8StringToNative( pszInternalCheckInterfaceVersions );
using var buffer = Helpers.Memory.Take();
var result = Native.SteamInternal_GameServer_Init_V2( unIP, usGamePort, usQueryPort, eServerMode, versionStr.Pointer, interfaceVersionsStr.Pointer, buffer.Ptr );
pOutErrMsg = Helpers.MemoryToString( buffer.Ptr );
return result;
}
}
}

@ -0,0 +1,114 @@

using System;
using System.Runtime.InteropServices;
namespace Facepunch.Steamworks
{
public partial class Client : BaseSteamworks
{
/// <summary>
/// Current user's Username
/// </summary>
public string Username { get; private set; }
/// <summary>
/// Current user's SteamId
/// </summary>
public ulong SteamId { get; private set; }
/// <summary>
/// Current Beta name, if ser
/// </summary>
public string BetaName { get; private set; }
public Voice Voice { get; internal set; }
public Client( uint appId )
{
Valve.Steamworks.SteamAPIInterop.SteamAPI_Init();
native = new Interop.NativeInterface();
//
// Get other interfaces
//
if ( !native.InitClient() )
{
native.Dispose();
native = null;
return;
}
//
// Set up warning hook callback
//
SteamAPIWarningMessageHook ptr = InternalOnWarning;
native.client.SetWarningMessageHook( Marshal.GetFunctionPointerForDelegate( ptr ) );
//
// Setup interfaces that client and server both have
//
SetupCommonInterfaces();
//
// Client only interfaces
//
Voice = new Voice( this );
//
// Cache common, unchanging info
//
AppId = appId;
Username = native.friends.GetPersonaName();
SteamId = native.user.GetSteamID();
BetaName = native.apps.GetCurrentBetaName();
//
// Run update, first call does some initialization
//
Update();
}
[UnmanagedFunctionPointer( CallingConvention.Cdecl )]
public delegate void SteamAPIWarningMessageHook( int nSeverity, System.Text.StringBuilder pchDebugText );
private void InternalOnWarning( int nSeverity, System.Text.StringBuilder text )
{
if ( OnMessage != null )
{
OnMessage( ( MessageType)nSeverity, text.ToString() );
}
}
/// <summary>
/// Should be called at least once every frame
/// </summary>
public override void Update()
{
if ( !IsValid )
return;
Valve.Steamworks.SteamAPI.RunCallbacks();
Voice.Update();
base.Update();
}
public override void Dispose()
{
if ( Voice != null )
{
Voice.Dispose();
Voice = null;
}
base.Dispose();
Valve.Interop.NativeEntrypoints.Extended.SteamAPI_Shutdown();
}
}
}

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Facepunch.Steamworks
{
public partial class Client : IDisposable
{
App _app;
public App App
{
get
{
if ( _app == null )
_app = new App( this );
return _app;
}
}
}
public class App
{
internal Client client;
internal App( Client c )
{
client = c;
}
public void MarkContentCorrupt( bool missingFilesOnly = false )
{
client.native.apps.MarkContentCorrupt( missingFilesOnly );
}
/// <summary>
/// Returns the current BuildId of the game.
/// This is pretty useless, as it isn't guarenteed to return
/// the build id you're playing, or the latest build id.
/// </summary>
public int BuildId
{
get
{
return client.native.apps.GetAppBuildId();
}
}
}
}

@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Facepunch.Steamworks
{
public partial class Client : IDisposable
{
Auth _auth;
public Auth Auth
{
get
{
if ( _auth == null )
_auth = new Auth{ client = this };
return _auth;
}
}
}
public class Auth
{
internal Client client;
public class Ticket : IDisposable
{
internal Client client;
public byte[] Data;
public uint Handle;
/// <summary>
/// Cancels a ticket.
/// You should cancel your ticket when you close the game or leave a server.
/// </summary>
public void Cancel()
{
if ( client.IsValid && Handle != 0 )
{
client.native.user.CancelAuthTicket( Handle );
Handle = 0;
Data = null;
}
}
public void Dispose()
{
Cancel();
}
}
/// <summary>
/// Creates an auth ticket.
/// Which you can send to a server to authenticate that you are who you say you are.
/// </summary>
public unsafe Ticket GetAuthSessionTicket()
{
var data = new byte[1024];
fixed ( byte* b = data )
{
uint ticketLength = 0;
uint ticket = client.native.user.GetAuthSessionTicket( (IntPtr) b, data.Length, ref ticketLength );
if ( ticket == 0 )
return null;
return new Ticket()
{
client = client,
Data = data.Take( (int)ticketLength ).ToArray(),
Handle = ticket
};
}
}
}
}

@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Valve.Steamworks;
namespace Facepunch.Steamworks
{
public partial class Client : IDisposable
{
Friends _friends;
public Friends Friends
{
get
{
if ( _friends == null )
_friends = new Friends( this );
return _friends;
}
}
}
public class SteamFriend
{
/// <summary>
/// Steam Id
/// </summary>
public ulong Id { get; internal set; }
/// <summary>
/// Return true if blocked
/// </summary>
public bool IsBlocked { get; internal set; }
/// <summary>
/// Return true if is a friend. Returns false if blocked, request etc.
/// </summary>
public bool IsFriend { get; internal set; }
/// <summary>
/// Their current display name
/// </summary>
public string Name;
/// <summary>
/// Returns true if this friend is online
/// </summary>
public bool IsOnline { get; internal set; }
/// <summary>
/// Returns true if this friend is online and playing this game
/// </summary>
public bool IsPlayingThisGame { get { return CurrentAppId == Client.AppId; } }
/// <summary>
/// Returns true if this friend is online and playing this game
/// </summary>
public bool IsPlaying { get { return CurrentAppId != 0; } }
/// <summary>
/// The AppId this guy is playing
/// </summary>
public ulong CurrentAppId { get; internal set; }
public uint ServerIp { get; internal set; }
public int ServerGamePort { get; internal set; }
public int ServerQueryPort { get; internal set; }
public ulong ServerLobbyId { get; internal set; }
internal Client Client { get; set; }
public void Refresh()
{
Name = Client.native.friends.GetFriendPersonaName( Id );
EFriendRelationship relationship = (EFriendRelationship) Client.native.friends.GetFriendRelationship( Id );
IsBlocked = relationship == EFriendRelationship.k_EFriendRelationshipBlocked;
IsFriend = relationship == EFriendRelationship.k_EFriendRelationshipFriend;
CurrentAppId = 0;
ServerIp = 0;
ServerGamePort = 0;
ServerQueryPort = 0;
ServerLobbyId = 0;
FriendGameInfo_t gameInfo = new FriendGameInfo_t();
if ( Client.native.friends.GetFriendGamePlayed( Id, out gameInfo ) && gameInfo.m_gameID > 0 )
{
CurrentAppId = gameInfo.m_gameID;
ServerIp = gameInfo.m_unGameIP;
ServerGamePort = gameInfo.m_usGamePort;
ServerQueryPort = gameInfo.m_usQueryPort;
ServerLobbyId = gameInfo.m_steamIDLobby;
}
}
}
public class Friends
{
internal Client client;
internal Friends( Client c )
{
client = c;
}
public string GetName( ulong steamid )
{
client.native.friends.RequestUserInformation( steamid, true );
return client.native.friends.GetFriendPersonaName( steamid );
}
private List<SteamFriend> _allFriends;
/// <summary>
/// Returns all friends, even blocked, ignored, friend requests etc
/// </summary>
public IEnumerable<SteamFriend> All
{
get
{
if ( _allFriends == null )
{
_allFriends = new List<SteamFriend>();
Refresh();
}
return _allFriends;
}
}
public IEnumerable<SteamFriend> AllFriends
{
get
{
foreach ( var friend in All )
{
if ( !friend.IsFriend ) continue;
yield return friend;
}
}
}
public IEnumerable<SteamFriend> AllBlocked
{
get
{
foreach ( var friend in All )
{
if ( !friend.IsBlocked ) continue;
yield return friend;
}
}
}
public void Refresh()
{
if ( _allFriends == null )
{
_allFriends = new List<SteamFriend>();
}
_allFriends.Clear();
var flags = (int) EFriendFlags.k_EFriendFlagAll;
var count = client.native.friends.GetFriendCount( flags );
for ( int i=0; i<count; i++ )
{
var steamid = client.native.friends.GetFriendByIndex( i, flags );
_allFriends.Add( Get( steamid ) );
}
}
public enum AvatarSize
{
/// <summary>
/// Should be 32x32 - but make sure to check!
/// </summary>
Small,
/// <summary>
/// Should be 64x64 - but make sure to check!
/// </summary>
Medium,
/// <summary>
/// Should be 184x184 - but make sure to check!
/// </summary>
Large
}
public Image GetAvatar( AvatarSize size, ulong steamid )
{
var imageid = 0;
switch ( size )
{
case AvatarSize.Small:
imageid = client.native.friends.GetSmallFriendAvatar( steamid );
break;
case AvatarSize.Medium:
imageid = client.native.friends.GetMediumFriendAvatar( steamid );
break;
case AvatarSize.Large:
imageid = client.native.friends.GetLargeFriendAvatar( steamid );
break;
}
var img = new Image()
{
Id = imageid
};
if ( imageid == 0 )
return img;
if ( img.TryLoad( client.native.utils ) )
return img;
throw new System.NotImplementedException( "Deferred Avatar Loading Todo" );
// Add to image loading list
//return img;
}
public SteamFriend Get( ulong steamid )
{
var f = new SteamFriend()
{
Id = steamid,
Client = client
};
f.Refresh();
return f;
}
}
}

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Valve.Steamworks;
namespace Facepunch.Steamworks
{
public class Image
{
public int Id { get; internal set; }
public int Width { get; internal set; }
public int Height { get; internal set; }
public byte[] Data { get; internal set; }
public bool IsLoaded { get; internal set; }
/// <summary>
/// Return true if this image couldn't be loaded for some reason
/// </summary>
public bool IsError { get; internal set; }
unsafe internal bool TryLoad( ISteamUtils utils )
{
if ( IsLoaded ) return true;
uint width = 0, height = 0;
if ( utils.GetImageSize( Id, ref width, ref height ) == false )
{
IsError = true;
return true;
}
var buffer = new byte[ width * height * 4 ];
fixed ( byte* ptr = buffer )
{
if ( utils.GetImageRGBA( Id, (IntPtr) ptr, buffer.Length ) == false )
{
IsError = true;
return true;
}
}
Width = (int) width;
Height = (int) height;
Data = buffer;
IsLoaded = true;
IsError = false;
return true;
}
public Color GetPixel( int x, int y )
{
if ( !IsLoaded ) throw new System.Exception( "Image not loaded" );
if ( x < 0 || x >= Width ) throw new System.Exception( "x out of bounds" );
if ( y < 0 || y >= Height ) throw new System.Exception( "y out of bounds" );
Color c = new Color();
var i = ( y * Width + x ) * 4;
c.r = Data[i + 0];
c.g = Data[i + 1];
c.b = Data[i + 2];
c.a = Data[i + 3];
return c;
}
}
public struct Color
{
public byte r, g, b, a;
}
}

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Facepunch.Steamworks
{
public partial class Client : IDisposable
{
private Overlay _overlay;
public Overlay Overlay
{
get
{
if ( _overlay == null )
_overlay = new Overlay { client = this };
return _overlay;
}
}
}
public class Overlay
{
internal Client client;
public void OpenUserPage( string name, ulong steamid ) { client.native.friends.ActivateGameOverlayToUser( name, steamid ); }
public void OpenProfile( ulong steamid ) { OpenUserPage( "steamid", steamid ); }
public void OpenChat( ulong steamid ){ OpenUserPage( "chat", steamid ); }
public void OpenTrade( ulong steamid ) { OpenUserPage( "jointrade", steamid ); }
public void OpenStats( ulong steamid ) { OpenUserPage( "stats", steamid ); }
public void OpenAchievements( ulong steamid ) { OpenUserPage( "achievements", steamid ); }
public void AddFriend( ulong steamid ) { OpenUserPage( "friendadd", steamid ); }
public void RemoveFriend( ulong steamid ) { OpenUserPage( "friendremove", steamid ); }
public void AcceptFriendRequest( ulong steamid ) { OpenUserPage( "friendrequestaccept", steamid ); }
public void IgnoreFriendRequest( ulong steamid ) { OpenUserPage( "friendrequestignore", steamid ); }
public void OpenUrl( string url ) { client.native.friends.ActivateGameOverlayToWebPage( url ); }
}
}

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Facepunch.Steamworks
{
public partial class Client : IDisposable
{
Screenshots _screenshots;
public Screenshots Screenshots
{
get
{
if ( _screenshots == null )
_screenshots = new Screenshots( this );
return _screenshots;
}
}
}
public class Screenshots
{
internal Client client;
internal Screenshots( Client c )
{
client = c;
}
public void Trigger()
{
client.native.screenshots.TriggerScreenshot();
}
}
}

@ -0,0 +1,105 @@
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;
using Valve.Steamworks;
namespace Facepunch.Steamworks
{
public class Server
{
internal Client client;
internal Server( Client c )
{
client = c;
}
public string Name { get; set; }
public int Ping { get; set; }
public string GameDir { get; set; }
public string Map { get; set; }
public string Description { get; set; }
public uint AppId { get; set; }
public int Players { get; set; }
public int MaxPlayers { get; set; }
public int BotPlayers { get; set; }
public bool Passworded { get; set; }
public bool Secure { get; set; }
public uint LastTimePlayed { get; set; }
public int Version { get; set; }
public string[] Tags { get; set; }
public ulong SteamId { get; set; }
public uint Address { get; set; }
public int ConnectionPort { get; set; }
public int QueryPort { get; set; }
public string AddressString
{
get
{
return string.Format( "{0}.{1}.{2}.{3}", ( Address >> 24 ) & 0xFFul, ( Address >> 16 ) & 0xFFul, ( Address >> 8 ) & 0xFFul, Address & 0xFFul );
}
}
public string ConnectionAddress
{
get
{
return string.Format( "{0}.{1}.{2}.{3}:{4}", ( Address >> 24 ) & 0xFFul, ( Address >> 16 ) & 0xFFul, ( Address >> 8 ) & 0xFFul, Address & 0xFFul, ConnectionPort );
}
}
internal static Server FromSteam( Client c, gameserveritem_t item )
{
return new Server( c )
{
Address = item.m_NetAdr.m_unIP,
ConnectionPort = item.m_NetAdr.m_usConnectionPort,
QueryPort = item.m_NetAdr.m_usQueryPort,
Name = item.m_szServerName,
Ping = item.m_nPing,
GameDir = item.m_szGameDir,
Map = item.m_szMap,
Description = item.m_szGameDescription,
AppId = item.m_nAppID,
Players = item.m_nPlayers,
MaxPlayers = item.m_nMaxPlayers,
BotPlayers = item.m_nBotPlayers,
Passworded = item.m_bPassword,
Secure = item.m_bSecure,
LastTimePlayed = item.m_ulTimeLastPlayed,
Version = item.m_nServerVersion,
Tags = item.m_szGameTags == null ? null : item.m_szGameTags.Split( ',' ),
SteamId = item.m_steamID
};
}
public Dictionary<string, string> Rules;
public Action OnServerRules;
public void UpdateRules()
{
//
//
// TEMPORARY, WE NEED TO WRITE OUR OWN VERSION OF THIS, DOESN'T WORK ON SPLIT PACKETS ETC
//
//
using ( var q = new SourceServerQuery( AddressString, ConnectionPort ) )
{
Rules = q.GetRules();
}
if ( OnServerRules != null && Rules != null )
OnServerRules();
}
}
}

@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Valve.Steamworks;
namespace Facepunch.Steamworks
{
public partial class ServerList
{
public class Request : IDisposable
{
internal Client client;
internal List<SubRequest> Requests = new List<SubRequest>();
internal class SubRequest
{
internal IntPtr Request;
internal int Pointer = 0;
internal List<int> WatchList = new List<int>();
internal bool Update( ISteamMatchmakingServers servers, Action<gameserveritem_t> OnServer, Action OnUpdate )
{
if ( Request == IntPtr.Zero )
return true;
bool changes = false;
//
// Add any servers we're not watching to our watch list
//
var count = servers.GetServerCount( Request );
if ( count != Pointer )
{
for ( int i = Pointer; i < count; i++ )
{
WatchList.Add( i );
}
}
Pointer = count;
//
// Remove any servers that respond successfully
//
WatchList.RemoveAll( x =>
{
var info = servers.GetServerDetails( Request, x );
if ( info.m_bHadSuccessfulResponse )
{
OnServer( info );
changes = true;
return true;
}
return false;
} );
//
// If we've finished refreshing
//
if ( servers.IsRefreshing( Request ) == false )
{
//
// Put any other servers on the 'no response' list
//
WatchList.RemoveAll( x =>
{
var info = servers.GetServerDetails( Request, x );
OnServer( info );
return true;
} );
servers.CancelQuery( Request );
Request = IntPtr.Zero;
changes = true;
}
if ( changes && OnUpdate != null )
OnUpdate();
return Request == IntPtr.Zero;
}
}
public Action OnUpdate;
/// <summary>
/// A list of servers that responded. If you're only interested in servers that responded since you
/// last updated, then simply clear this list.
/// </summary>
public List<Server> Responded = new List<Server>();
/// <summary>
/// A list of servers that were in the master list but didn't respond.
/// </summary>
public List<Server> Unresponsive = new List<Server>();
/// <summary>
/// True when we have finished
/// </summary>
public bool Finished = false;
internal Request( Client c )
{
client = c;
client.OnUpdate += Update;
}
~Request()
{
Dispose();
}
internal IEnumerable<string> ServerList { get; set; }
internal void StartCustomQuery()
{
if ( ServerList == null )
return;
int blockSize = 16;
int Pointer = 0;
while ( true )
{
var sublist = ServerList.Skip( Pointer ).Take( blockSize );
if ( sublist.Count() == 0 )
break;
Pointer += sublist.Count();
var filter = new Filter();
filter.Add( "or", sublist.Count().ToString() );
foreach ( var server in sublist )
{
filter.Add( "gameaddr", server );
}
filter.Start();
var id = client.native.servers.RequestInternetServerList( client.AppId, filter.NativeArray, filter.Count, IntPtr.Zero );
filter.Free();
AddRequest( id );
}
ServerList = null;
}
internal void AddRequest( IntPtr id )
{
Requests.Add( new SubRequest() { Request = id } );
}
private void Update()
{
if ( Requests.Count == 0 )
return;
for( int i=0; i< Requests.Count(); i++ )
{
if ( Requests[i].Update( client.native.servers, OnServer, OnUpdate ) )
{
Requests.RemoveAt( i );
i--;
}
}
if ( Requests.Count == 0 )
{
Finished = true;
client.OnUpdate -= Update;
}
}
private void OnServer( gameserveritem_t info )
{
if ( info.m_bHadSuccessfulResponse )
{
Responded.Add( Server.FromSteam( client, info ) );
}
else
{
Unresponsive.Add( Server.FromSteam( client, info ) );
}
}
/// <summary>
/// Disposing will end the query
/// </summary>
public void Dispose()
{
client.OnUpdate -= Update;
//
// Cancel the query if it's still running
//
foreach( var subRequest in Requests )
{
if ( client.IsValid )
client.native.servers.CancelQuery( subRequest.Request );
}
Requests.Clear();
}
}
}
}

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Valve.Steamworks;
namespace Facepunch.Steamworks
{
public partial class ServerList
{
public class Server
{
public string Name { get; set; }
public int Ping { get; set; }
public string GameDir { get; set; }
public string Map { get; set; }
public string Description { get; set; }
public uint AppId { get; set; }
public int Players { get; set; }
public int MaxPlayers { get; set; }
public int BotPlayers { get; set; }
public bool Passworded { get; set; }
public bool Secure { get; set; }
public uint LastTimePlayed { get; set; }
public int Version { get; set; }
public string[] Tags { get; set; }
public ulong SteamId { get; set; }
public uint Address { get; set; }
public int ConnectionPort { get; set; }
public int QueryPort { get; set; }
internal Client Client;
public string AddressString
{
get
{
return string.Format( "{0}.{1}.{2}.{3}", ( Address >> 24 ) & 0xFFul, ( Address >> 16 ) & 0xFFul, ( Address >> 8 ) & 0xFFul, Address & 0xFFul );
}
}
public string ConnectionAddress
{
get
{
return string.Format( "{0}.{1}.{2}.{3}:{4}", ( Address >> 24 ) & 0xFFul, ( Address >> 16 ) & 0xFFul, ( Address >> 8 ) & 0xFFul, Address & 0xFFul, ConnectionPort );
}
}
internal static Server FromSteam( Client client, gameserveritem_t item )
{
return new Server()
{
Client = client,
Address = item.m_NetAdr.m_unIP,
ConnectionPort = item.m_NetAdr.m_usConnectionPort,
QueryPort = item.m_NetAdr.m_usQueryPort,
Name = item.m_szServerName,
Ping = item.m_nPing,
GameDir = item.m_szGameDir,
Map = item.m_szMap,
Description = item.m_szGameDescription,
AppId = item.m_nAppID,
Players = item.m_nPlayers,
MaxPlayers = item.m_nMaxPlayers,
BotPlayers = item.m_nBotPlayers,
Passworded = item.m_bPassword,
Secure = item.m_bSecure,
LastTimePlayed = item.m_ulTimeLastPlayed,
Version = item.m_nServerVersion,
Tags = item.m_szGameTags == null ? null : item.m_szGameTags.Split( ',' ),
SteamId = item.m_steamID
};
}
/// <summary>
/// Callback when rules are receieved.
/// The bool is true if server responded properly.
/// </summary>
public Action<bool> OnReceivedRules;
/// <summary>
/// List of server rules. Use HasRules to see if this is safe to access.
/// </summary>
public Dictionary<string, string> Rules;
/// <summary>
/// Returns true if this server has rules
/// </summary>
public bool HasRules { get { return Rules != null && Rules.Count > 0; } }
internal Interop.ServerRules RulesRequest;
/// <summary>
/// Populates Rules for this server
/// </summary>
public void FetchRules()
{
if ( RulesRequest != null )
return;
Rules = new Dictionary<string, string>();
RulesRequest = new Interop.ServerRules( this, Address, QueryPort );
}
internal void OnServerRulesReceiveFinished( bool Success )
{
RulesRequest.Dispose();
RulesRequest = null;
if ( OnReceivedRules != null )
OnReceivedRules( Success );
}
}
}
}

@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Valve.Steamworks;
namespace Facepunch.Steamworks
{
public partial class Client : IDisposable
{
private ServerList _serverlist;
public ServerList ServerList
{
get
{
if ( _serverlist == null )
_serverlist = new ServerList { client = this };
return _serverlist;
}
}
}
public partial class ServerList
{
public class Filter : List<KeyValuePair<string, string>>
{
public void Add( string k, string v )
{
Add( new KeyValuePair<string, string>( k, v ) );
}
internal IntPtr NativeArray;
private IntPtr m_pArrayEntries;
internal void Start()
{
var filters = this.Select( x =>
{
return new MatchMakingKeyValuePair_t()
{
m_szKey = x.Key,
m_szValue = x.Value
};
} ).ToArray();
int sizeOfMMKVP = Marshal.SizeOf(typeof(MatchMakingKeyValuePair_t));
NativeArray = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( IntPtr ) ) * filters.Length );
m_pArrayEntries = Marshal.AllocHGlobal( sizeOfMMKVP * filters.Length );
for ( int i = 0; i < filters.Length; ++i )
{
Marshal.StructureToPtr( filters[i], new IntPtr( m_pArrayEntries.ToInt64() + ( i * sizeOfMMKVP ) ), false );
}
Marshal.WriteIntPtr( NativeArray, m_pArrayEntries );
}
internal void Free()
{
if ( m_pArrayEntries != IntPtr.Zero )
{
Marshal.FreeHGlobal( m_pArrayEntries );
}
if ( NativeArray != IntPtr.Zero )
{
Marshal.FreeHGlobal( NativeArray );
}
}
}
internal Client client;
[StructLayout( LayoutKind.Sequential )]
private struct MatchPair
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string key;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string value;
}
public Request Internet( Filter filter )
{
filter.Start();
var request = new Request( client );
request.AddRequest( client.native.servers.RequestInternetServerList( client.AppId, filter.NativeArray, filter.Count, IntPtr.Zero ) );
filter.Free();
return request;
}
public Request Custom( IEnumerable<string> serverList )
{
var request = new Request( client );
request.ServerList = serverList;
request.StartCustomQuery();
return request;
}
/// <summary>
/// History filters don't seem to work, so we don't bother.
/// You should apply them post process'dly
/// </summary>
public Request History()
{
var request = new Request( client );
request.AddRequest( client.native.servers.RequestHistoryServerList( client.AppId, new IntPtr[] { }, IntPtr.Zero ) );
return request;
}
/// <summary>
/// Favourite filters don't seem to work, so we don't bother.
/// You should apply them post process'dly
/// </summary>
public Request Favourites()
{
var request = new Request( client );
request.AddRequest( client.native.servers.RequestFavoritesServerList( client.AppId, new IntPtr[] { }, IntPtr.Zero ) );
return request;
}
public void AddToHistory( Server server )
{
// client.native.matchmaking
}
public void RemoveFromHistory( Server server )
{
//
}
public void AddToFavourite( Server server )
{
// client.native.matchmaking
}
public void RemoveFromFavourite( Server server )
{
//
}
public bool IsFavourite( Server server )
{
return false;
}
}
}

@ -0,0 +1,509 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
internal class SourceServerQuery :IDisposable
{
public class PlayersResponse
{
public short player_count;
public List<Player> players = new List<Player>();
public class Player
{
public String name { get; set; }
public int score { get; set; }
public float playtime { get; set; }
}
}
private IPEndPoint endPoint;
private Socket socket;
private UdpClient client;
// send & receive timeouts
private int send_timeout = 2500;
private int receive_timeout = 2500;
// raw response returned from the server
private byte[] raw_data;
private int offset = 0;
// constants
private readonly byte[] FFFFFFFF = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
public SourceServerQuery( String ip, int port )
{
this.endPoint = new IPEndPoint( IPAddress.Parse( ip ), port );
}
/// <summary>
/// Get a list of currently in-game clients on the specified gameserver.
/// <b>Please note:</b> the playtime is stored as a float in <i>seconds</i>, you might want to convert it.
///
/// See https://developer.valvesoftware.com/wiki/Server_queries#A2S_PLAYER for more Information
/// </summary>
/// <returns>A PLayersResponse Object containing the name, score and playtime of each player</returns>
public PlayersResponse GetPlayerList()
{
// open socket if not already open
this.GetSocket();
// we don't need the header, so set pointer to where the payload begins
this.offset = 5;
try
{
PlayersResponse pr = new PlayersResponse();
// since A2S_PLAYER requests require a valid challenge, get it first
byte[] challenge = this.GetChallenge(0x55, true);
byte[] request = new byte[challenge.Length + this.FFFFFFFF.Length + 1];
Array.Copy( this.FFFFFFFF, 0, request, 0, this.FFFFFFFF.Length );
request[this.FFFFFFFF.Length] = 0x55;
Array.Copy( challenge, 0, request, this.FFFFFFFF.Length + 1, challenge.Length );
this.socket.Send( request );
// MODIFIED BY ZACKBOE
// Increased byte size from 1024 in order to receive more player data
// Previously returned a socket exception at >~ 51 players.
this.raw_data = new byte[2048];
// END MODIFICATION
this.socket.Receive( this.raw_data );
byte player_count = this.ReadByte();
// fill up the list of players
for ( int i = 0; i < player_count; i++ )
{
this.ReadByte();
PlayersResponse.Player p = new PlayersResponse.Player();
p.name = this.ReadString();
p.score = this.ReadInt32();
p.playtime = this.ReadFloat();
pr.players.Add( p );
}
pr.player_count = player_count;
return pr;
}
catch ( SocketException e )
{
return null;
}
}
/// <summary>
/// Get a list of all publically available CVars ("rules") from the server.
/// <b>Note:</b> Due to a bug in the Source Engine, it might happen that some CVars/values are cut off.
///
/// Example: mp_idlemaxtime = [nothing]
/// Only Valve can fix that.
/// </summary>
/// <returns>A RulesResponse Object containing a Name-Value pair of each CVar</returns>
public Dictionary<string, string> GetRules()
{
// open udpclient if not already open
this.GetClient();
try
{
var d = new Dictionary<string, string>();
// similar to A2S_PLAYER requests, A2S_RULES require a valid challenge
byte[] challenge = this.GetChallenge(0x56, false);
byte[] request = new byte[challenge.Length + this.FFFFFFFF.Length + 1];
Array.Copy( this.FFFFFFFF, 0, request, 0, this.FFFFFFFF.Length );
request[this.FFFFFFFF.Length] = 0x56;
Array.Copy( challenge, 0, request, this.FFFFFFFF.Length + 1, challenge.Length );
this.client.Send( request, request.Length );
//
// Since A2S_RULES responses might be split up into several packages/compressed, we have to do a special handling of them
//
int bytesRead;
// this will keep our assembled message
byte[] buffer = new byte[4096];
// send first request
this.raw_data = this.client.Receive( ref this.endPoint );
bytesRead = this.raw_data.Length;
// reset pointer
this.offset = 0;
int is_split = this.ReadInt32();
int requestid = this.ReadInt32();
this.offset = 4;
// response is split up into several packets
if ( this.PacketIsSplit( is_split ) )
{
bool isCompressed = false;
byte[] splitData;
int packetCount, packetNumber, requestId;
int packetsReceived = 1;
int packetChecksum = 0;
int packetSplit = 0;
short splitSize;
int uncompressedSize = 0;
List<byte[]> splitPackets = new List<byte[]>();
do
{
// unique request id
requestId = this.ReverseBytes( this.ReadInt32() );
isCompressed = this.PacketIsCompressed( requestId );
packetCount = this.ReadByte();
packetNumber = this.ReadByte() + 1;
// so we know how big our byte arrays have to be
splitSize = this.ReadInt16();
splitSize -= 4; // fix
if ( packetsReceived == 1 )
{
for ( int i = 0; i < packetCount; i++ )
{
splitPackets.Add( new byte[] { } );
}
}
// if the packets are compressed, get some data to decompress them
if ( isCompressed )
{
uncompressedSize = ReverseBytes( this.ReadInt32() );
packetChecksum = ReverseBytes( this.ReadInt32() );
}
// ommit header in first packet
if ( packetNumber == 1 ) this.ReadInt32();
splitData = new byte[splitSize];
splitPackets[packetNumber - 1] = this.ReadBytes();
// fixes a case where the returned package might still contain a character after the last \0 terminator (truncated name => value)
// please note: this therefore also removes the value of said variable, but atleast the program won't crash
if ( splitPackets[packetNumber - 1].Length - 1 > 0 && splitPackets[packetNumber - 1][splitPackets[packetNumber - 1].Length - 1] != 0x00 )
{
splitPackets[packetNumber - 1][splitPackets[packetNumber - 1].Length - 1] = 0x00;
}
// reset pointer again, so we can copy over the contents
this.offset = 0;
if ( packetsReceived < packetCount )
{
this.raw_data = this.client.Receive( ref this.endPoint );
bytesRead = this.raw_data.Length;
// continue with the next packets
packetSplit = this.ReadInt32();
packetsReceived++;
}
else
{
// all packets received
bytesRead = 0;
}
}
while ( packetsReceived <= packetCount && bytesRead > 0 && packetSplit == -2 );
// decompress
if ( isCompressed )
{
buffer = ReassemblePacket( splitPackets, true, uncompressedSize, packetChecksum );
}
else
{
buffer = ReassemblePacket( splitPackets, false, 0, 0 );
}
}
else
{
buffer = this.raw_data;
}
// move our final result over to handle it
this.raw_data = buffer;
// omitting header
this.offset += 1;
var count = this.ReadInt16();
for ( int i = 0; i < count; i++ )
{
var k = this.ReadString();
var v = this.ReadString();
if ( !d.ContainsKey( k ) )
d.Add( k, v );
}
return d;
}
catch ( SocketException e )
{
Console.WriteLine( e );
return null;
}
}
/// <summary>
/// Close all currently open socket/UdpClient connections
/// </summary>
public void Dispose()
{
if ( this.socket != null ) this.socket.Close();
if ( this.client != null ) this.client.Close();
}
/// <summary>
/// Open up a new Socket-based connection to a server, if not already open.
/// </summary>
private void GetSocket()
{
if ( this.socket == null )
{
this.socket = new Socket(
AddressFamily.InterNetwork,
SocketType.Dgram,
ProtocolType.Udp );
this.socket.SendTimeout = this.send_timeout;
this.socket.ReceiveTimeout = this.receive_timeout;
this.socket.Connect( this.endPoint );
}
}
/// <summary>
/// Create a new UdpClient connection to a server (mostly used for multi-packet answers)
/// </summary>
private void GetClient()
{
if ( this.client == null )
{
this.client = new UdpClient();
this.client.Connect( this.endPoint );
this.client.DontFragment = true;
this.client.Client.SendTimeout = this.send_timeout;
this.client.Client.ReceiveTimeout = this.receive_timeout;
}
}
/// <summary>
/// Reassmble a multi-packet response.
/// </summary>
/// <param name="splitPackets">The packets to assemble</param>
/// <param name="isCompressed">true: packets are compressed; false: not</param>
/// <param name="uncompressedSize">The size of the message after decompression (for comparison)</param>
/// <param name="packetChecksum">Validation of the result</param>
/// <returns>A byte-array containing all packets assembled together/decompressed.</returns>
private byte[] ReassemblePacket( List<byte[]> splitPackets, bool isCompressed, int uncompressedSize, int packetChecksum )
{
byte[] packetData, tmpData;
packetData = new byte[0];
foreach ( byte[] splitPacket in splitPackets )
{
if ( splitPacket == null )
{
throw new Exception();
}
tmpData = packetData;
packetData = new byte[tmpData.Length + splitPacket.Length];
MemoryStream memStream = new MemoryStream(packetData);
memStream.Write( tmpData, 0, tmpData.Length );
memStream.Write( splitPacket, 0, splitPacket.Length );
}
if ( isCompressed )
{
throw new System.NotImplementedException();
}
return packetData;
}
/// <summary>
/// Invert the Byte-order Mark of an value, used for compatibility between Little <-> Large BOM
/// </summary>
/// <param name="value">The value to invert</param>
/// <returns>BOM-inversed value (if needed), otherwise the original value</returns>
private int ReverseBytes( int value )
{
byte[] bytes = BitConverter.GetBytes(value);
if ( BitConverter.IsLittleEndian )
{
Array.Reverse( bytes );
}
return BitConverter.ToInt32( bytes, 0 );
}
/// <summary>
/// Determine whetever or not a message is compressed.
/// Simply detects if the most significant bit is 1.
/// </summary>
/// <param name="value">The value to check</param>
/// <returns>true, if message is compressed, false otherwise</returns>
private bool PacketIsCompressed( int value )
{
return ( value & 0x8000 ) != 0;
}
/// <summary>
/// Determine whetever or not a message is split up.
/// </summary>
/// <param name="paket">The value to check</param>
/// <returns>true, if message is split up, false otherwise</returns>
private bool PacketIsSplit( int paket )
{
return ( paket == -2 );
}
/// <summary>
/// Request the 4-byte challenge id from the server, required for A2S_RULES and A2S_PLAYER.
/// </summary>
/// <param name="type">The type of message to request the challenge for (see constants)</param>
/// <param name="socket">Request method to use (performance reasons)</param>
/// <returns>A Byte Array (4-bytes) containing the challenge</returns>
private Byte[] GetChallenge( byte type, bool socket = true )
{
byte[] request = new byte[this.FFFFFFFF.Length + this.FFFFFFFF.Length + 1];
Array.Copy( this.FFFFFFFF, 0, request, 0, this.FFFFFFFF.Length );
request[FFFFFFFF.Length] = type;
Array.Copy( this.FFFFFFFF, 0, request, this.FFFFFFFF.Length + 1, this.FFFFFFFF.Length );
byte[] raw_response = new byte[24];
byte[] challenge = new byte[4];
// using sockets
if ( socket )
{
this.socket.Send( request );
this.socket.Receive( raw_response );
}
else
{
this.client.Send( request, request.Length );
raw_response = this.client.Receive( ref this.endPoint );
}
Array.Copy( raw_response, 5, challenge, 0, 4 ); // change this valve modifies the protocol!
return challenge;
}
/// <summary>
/// Read a single byte value from our raw data.
/// </summary>
/// <returns>A single Byte at the next Offset Address</returns>
private Byte ReadByte()
{
byte[] b = new byte[1];
Array.Copy( this.raw_data, this.offset, b, 0, 1 );
this.offset++;
return b[0];
}
/// <summary>
/// Read all remaining Bytes from our raw data.
/// Used for multi-packet responses.
/// </summary>
/// <returns>All remaining data</returns>
private Byte[] ReadBytes()
{
int size = (this.raw_data.Length - this.offset - 4);
if ( size < 1 ) return new Byte[] { };
byte[] b = new byte[size];
Array.Copy( this.raw_data, this.offset, b, 0, this.raw_data.Length - this.offset - 4 );
this.offset += ( this.raw_data.Length - this.offset - 4 );
return b;
}
/// <summary>
/// Read a 32-Bit Integer value from the next offset address.
/// </summary>
/// <returns>The Int32 Value found at the offset address</returns>
private Int32 ReadInt32()
{
byte[] b = new byte[4];
Array.Copy( this.raw_data, this.offset, b, 0, 4 );
this.offset += 4;
return BitConverter.ToInt32( b, 0 );
}
/// <summary>
/// Read a 16-Bit Integer (also called "short") value from the next offset address.
/// </summary>
/// <returns>The Int16 Value found at the offset address</returns>
private Int16 ReadInt16()
{
byte[] b = new byte[2];
Array.Copy( this.raw_data, this.offset, b, 0, 2 );
this.offset += 2;
return BitConverter.ToInt16( b, 0 );
}
/// <summary>
/// Read a Float value from the next offset address.
/// </summary>
/// <returns>The Float Value found at the offset address</returns>
private float ReadFloat()
{
byte[] b = new byte[4];
Array.Copy( this.raw_data, this.offset, b, 0, 4 );
this.offset += 4;
return BitConverter.ToSingle( b, 0 );
}
/// <summary>
/// Read a String until its end starting from the next offset address.
/// Reading stops once the method detects a 0x00 Character at the next position (\0 terminator)
/// </summary>
/// <returns>The String read</returns>
private String ReadString()
{
byte[] cache = new byte[1] { 0x01 };
String output = "";
while ( cache[0] != 0x00 )
{
if ( this.offset == this.raw_data.Length ) break; // fixes Valve's inability to code a proper query protocol
Array.Copy( this.raw_data, this.offset, cache, 0, 1 );
this.offset++;
if ( cache[0] != 0x00)
output += Encoding.UTF8.GetString( cache );
}
return output;
}
}

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Facepunch.Steamworks
{
public partial class Client : IDisposable
{
Stats _stats;
public Stats Stats
{
get
{
if ( _stats == null )
_stats = new Stats( this );
return _stats;
}
}
}
public class Stats
{
internal Client client;
internal Stats( Client c )
{
client = c;
}
public void UpdateStats()
{
client.native.userstats.RequestCurrentStats();
}
public void UpdateGlobalStats( int days = 1 )
{
client.native.userstats.GetNumberOfCurrentPlayers();
client.native.userstats.RequestGlobalAchievementPercentages();
client.native.userstats.RequestGlobalStats( days );
}
public int GetInt( string name )
{
int data = 0;
client.native.userstats.GetStat( name, ref data );
return data;
}
public long GetGlobalInt( string name )
{
long data = 0;
client.native.userstats.GetGlobalStat( name, ref data );
return data;
}
public float GetFloat( string name )
{
float data = 0;
client.native.userstats.GetStat0( name, ref data );
return data;
}
public double GetGlobalFloat( string name )
{
double data = 0;
client.native.userstats.GetGlobalStat0( name, ref data );
return data;
}
}
}

@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Facepunch.Steamworks
{
public class Voice : IDisposable
{
const int ReadBufferSize = 1024 * 128;
const int UncompressBufferSize = 1024 * 256;
internal Client client;
internal IntPtr ReadCompressedBuffer;
internal IntPtr ReadUncompressedBuffer;
internal IntPtr UncompressBuffer;
public Action<IntPtr, int> OnCompressedData;
public Action<IntPtr, int> OnUncompressedData;
/// <summary>
/// Returns the optimal sample rate for voice - according to Steam
/// </summary>
public uint OptimalSampleRate
{
get { return client.native.user.GetVoiceOptimalSampleRate(); }
}
private bool _wantsrecording = false;
/// <summary>
/// If set to true we are listening to the mic.
/// You should usually toggle this with the press of a key for push to talk.
/// </summary>
public bool WantsRecording
{
get { return _wantsrecording; }
set
{
_wantsrecording = value;
if ( value )
{
client.native.user.StartVoiceRecording();
}
else
{
client.native.user.StopVoiceRecording();
}
}
}
/// <summary>
/// The last time voice was detected, recorded
/// </summary>
public DateTime LastVoiceRecordTime { get; private set; }
public TimeSpan TimeSinceLastVoiceRecord { get { return DateTime.Now.Subtract( LastVoiceRecordTime ); } }
public bool IsRecording = false;
/// <summary>
/// If set we will capture the audio at this rate. If unset (set to 0) will capture at OptimalSampleRate
/// </summary>
public uint DesiredSampleRate = 0;
public Voice( Client client )
{
this.client = client;
ReadCompressedBuffer = Marshal.AllocHGlobal( ReadBufferSize );
ReadUncompressedBuffer = Marshal.AllocHGlobal( ReadBufferSize );
UncompressBuffer = Marshal.AllocHGlobal( UncompressBufferSize );
}
public void Dispose()
{
Marshal.FreeHGlobal( ReadCompressedBuffer );
Marshal.FreeHGlobal( ReadUncompressedBuffer );
Marshal.FreeHGlobal( UncompressBuffer );
}
internal void Update()
{
if ( OnCompressedData == null && OnUncompressedData == null )
return;
uint bufferRegularLastWrite = 0;
uint bufferCompressedLastWrite = 0;
Valve.Steamworks.EVoiceResult result = (Valve.Steamworks.EVoiceResult) client.native.user.GetVoice( OnCompressedData != null, ReadCompressedBuffer, ReadBufferSize, ref bufferCompressedLastWrite,
OnUncompressedData != null, (IntPtr) ReadUncompressedBuffer, ReadBufferSize, ref bufferRegularLastWrite,
DesiredSampleRate == 0 ? OptimalSampleRate : DesiredSampleRate );
Console.WriteLine( result );
IsRecording = true;
if ( result == Valve.Steamworks.EVoiceResult.k_EVoiceResultOK )
{
if ( OnCompressedData != null && bufferCompressedLastWrite > 0 )
{
OnCompressedData( ReadCompressedBuffer, (int)bufferCompressedLastWrite );
}
if ( OnUncompressedData != null && bufferRegularLastWrite > 0 )
{
OnUncompressedData( ReadUncompressedBuffer, (int)bufferRegularLastWrite );
}
LastVoiceRecordTime = DateTime.Now;
}
if ( result == Valve.Steamworks.EVoiceResult.k_EVoiceResultNotRecording ||
result == Valve.Steamworks.EVoiceResult.k_EVoiceResultNotInitialized )
IsRecording = false;
}
public unsafe bool Decompress( byte[] input, MemoryStream output, uint samepleRate = 0 )
{
fixed ( byte* p = input )
{
return Decompress( (IntPtr)p, 0, input.Length, output, samepleRate );
}
}
public unsafe bool Decompress( IntPtr input, int inputoffset, int inputsize, MemoryStream output, uint samepleRate = 0 )
{
if ( samepleRate == 0 )
samepleRate = OptimalSampleRate;
uint bytesOut = 0;
var result = (Valve.Steamworks.EVoiceResult) client.native.user.DecompressVoice( (IntPtr)( ((byte*)input) + inputoffset ), (uint) inputsize, UncompressBuffer, UncompressBufferSize, ref bytesOut, samepleRate );
if ( bytesOut > 0 )
output.SetLength( bytesOut );
if ( result == Valve.Steamworks.EVoiceResult.k_EVoiceResultOK )
{
if ( output.Capacity < bytesOut )
output.Capacity = (int) bytesOut;
output.SetLength( bytesOut );
Marshal.Copy( UncompressBuffer, output.GetBuffer(), 0, (int) bytesOut );
return true;
}
return false;
}
}
}

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Facepunch.Steamworks
{
public static class Config
{
/// <summary>
/// Some platforms allow/need CallingConvention.ThisCall. If you're crashing with argument null
/// errors on certain platforms, try flipping this to true.
///
/// I owe this logic to Riley Labrecque's hard work on Steamworks.net - I don't have the knowledge
/// or patience to find this shit on my own, so massive thanks to him. And also massive thanks to him
/// for releasing his shit open source under the MIT license so we can all learn and iterate.
///
/// </summary>
public static bool UseThisCall { get; set; } = true;
/// <summary>
/// Set this to true on Linux and OSX
/// </summary>
public static bool PackSmall { get; set; } = false;
/// <summary>
/// The Native dll to look for. This is the steam_api.dll renamed.
/// We need to rename the dll anyway because we can't dynamically choose the library
/// ie, we can't load steam_api64.dll on windows 64 platforms. So instead we choose to
/// keep the library name the same.
///
/// This is exposed only for the benefit of implementation - and cannot be changed at runtime.
/// </summary>
public const string LibraryName = "FacepunchSteamworksApi";
}
}

@ -1,20 +0,0 @@
namespace Steamworks.Data
{
public enum LeaderboardDisplay : int
{
/// <summary>
/// The score is just a simple numerical value
/// </summary>
Numeric = 1,
/// <summary>
/// The score represents a time, in seconds
/// </summary>
TimeSeconds = 2,
/// <summary>
/// The score represents a time, in milliseconds
/// </summary>
TimeMilliSeconds = 3,
}
}

@ -1,15 +0,0 @@
namespace Steamworks.Data
{
public enum LeaderboardSort : int
{
/// <summary>
/// The top-score is the lowest number
/// </summary>
Ascending = 1,
/// <summary>
/// The top-score is the highest number
/// </summary>
Descending = 2,
}
}

@ -1,42 +0,0 @@
using System;
namespace Steamworks.Data
{
[Flags]
public enum SendType : int
{
/// <summary>
/// Send the message unreliably. Can be lost. Messages *can* be larger than a
/// single MTU (UDP packet), but there is no retransmission, so if any piece
/// of the message is lost, the entire message will be dropped.
///
/// The sending API does have some knowledge of the underlying connection, so
/// if there is no NAT-traversal accomplished or there is a recognized adjustment
/// happening on the connection, the packet will be batched until the connection
/// is open again.
/// </summary>
Unreliable = 0,
/// <summary>
/// Disable Nagle's algorithm.
/// By default, Nagle's algorithm is applied to all outbound messages. This means
/// that the message will NOT be sent immediately, in case further messages are
/// sent soon after you send this, which can be grouped together. Any time there
/// is enough buffered data to fill a packet, the packets will be pushed out immediately,
/// but partially-full packets not be sent until the Nagle timer expires.
/// </summary>
NoNagle = 1 << 0,
/// <summary>
/// If the message cannot be sent very soon (because the connection is still doing some initial
/// handshaking, route negotiations, etc), then just drop it. This is only applicable for unreliable
/// messages. Using this flag on reliable messages is invalid.
/// </summary>
NoDelay = 1 << 2,
/// Reliable message send. Can send up to 0.5mb in a single message.
/// Does fragmentation/re-assembly of messages under the hood, as well as a sliding window for
/// efficient sends of large chunks of data.
Reliable = 1 << 3
}
}

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Facepunch.Steamworks.Posix</AssemblyName>
<DefineConstants>$(DefineConstants);PLATFORM_POSIX</DefineConstants>
<TargetFrameworks>netstandard2.1;net6.0;net46</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>10</LangVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>Steamworks</RootNamespace>
</PropertyGroup>
<Import Project="Facepunch.Steamworks.targets" />
</Project>

@ -1,42 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Facepunch.Steamworks.Win32</AssemblyName>
<DefineConstants>$(DefineConstants);PLATFORM_WIN32;PLATFORM_WIN</DefineConstants>
<TargetFrameworks>netstandard2.1;net6.0;net46</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<RootNamespace>Steamworks</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<FrameworkPathOverride Condition="'$(TargetFramework)' == 'net40'">C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client</FrameworkPathOverride>
<Authors>Garry Newman</Authors>
<PackageId>Facepunch.Steamworks.win32</PackageId>
<PackageDescription>Steamworks implementation with an emphasis on making things easy. For Windows x86.</PackageDescription>
<PackageProjectUrl>https://github.com/Facepunch/Facepunch.Steamworks</PackageProjectUrl>
<PackageIcon>Facepunch.Steamworks.jpg</PackageIcon>
<PackageTags>facepunch;steam;unity;steamworks;valve</PackageTags>
<LangVersion>10</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/Facepunch/Facepunch.Steamworks.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<None Include="Facepunch.Steamworks.jpg">
<Pack>true</Pack>
<PackagePath>/</PackagePath>
</None>
<None Include="steam_api.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Pack>true</Pack>
<PackagePath>content</PackagePath>
</None>
</ItemGroup>
<Import Project="Facepunch.Steamworks.targets" />
</Project>

@ -1,55 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Facepunch.Steamworks.Win64</AssemblyName>
<DefineConstants>$(DefineConstants);PLATFORM_WIN64;PLATFORM_WIN;PLATFORM_64</DefineConstants>
<TargetFrameworks>netstandard2.1;net6.0;net46</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<RootNamespace>Steamworks</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<FrameworkPathOverride Condition="'$(TargetFramework)' == 'net40'">C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client</FrameworkPathOverride>
<Authors>Garry Newman</Authors>
<PackageId>Facepunch.Steamworks</PackageId>
<PackageDescription>Steamworks implementation with an emphasis on making things easy. For Windows x64.</PackageDescription>
<PackageProjectUrl>https://github.com/Facepunch/Facepunch.Steamworks</PackageProjectUrl>
<PackageIcon>Facepunch.Steamworks.jpg</PackageIcon>
<PackageTags>facepunch;steam;unity;steamworks;valve</PackageTags>
<LangVersion>10</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/Facepunch/Facepunch.Steamworks.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<None Include="Facepunch.Steamworks.jpg">
<Pack>true</Pack>
<PackagePath>/</PackagePath>
</None>
<None Include="steam_api64.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Pack>true</Pack>
<PackagePath>content</PackagePath>
</None>
</ItemGroup>
<Import Project="Facepunch.Steamworks.targets" />
<Target Name="PostBuildHome" AfterTargets="PostBuildEvent" Condition="'$(Computername)'=='GarryBasementPc'">
<Exec Command="Copy $(TargetDir)\Facepunch.Steamworks.Win64.* C:\plastic\RustMain\Assets\Plugins\Facepunch.Steamworks\" />
<Exec Command="Copy $(TargetDir)\Facepunch.Steamworks.Posix.* C:\plastic\RustMain\Assets\Plugins\Facepunch.Steamworks\" />
</Target>
<Target Name="PostBuildOffice" AfterTargets="PostBuildEvent" Condition="'$(Computername)'=='GARRYSOFFICEPC'">
<Exec Command="Copy $(TargetDir)\Facepunch.Steamworks.Win64.* C:\Plastic\Rust\Assets\Plugins\Facepunch.Steamworks\" />
<Exec Command="Copy $(TargetDir)\Facepunch.Steamworks.Posix.* C:\Plastic\Rust\Assets\Plugins\Facepunch.Steamworks\" />
<Exec Command="Copy $(TargetDir)\Facepunch.Steamworks.Win64.* C:\Git\Facepunch.Steamworks.UnityTest\Assets\Steamworks" />
<Exec Command="Copy $(TargetDir)\Facepunch.Steamworks.Win32.* C:\Git\Facepunch.Steamworks.UnityTest\Assets\Steamworks" />
<Exec Command="Copy $(TargetDir)\Facepunch.Steamworks.Posix.* C:\Git\Facepunch.Steamworks.UnityTest\Assets\Steamworks" />
</Target>
</Project>

Some files were not shown because too many files have changed in this diff Show More