1
0
mirror of https://github.com/xPaw/PHP-Source-Query.git synced 2026-05-18 15:13:33 +02:00

101 Commits

Author SHA1 Message Date
Pavel Djundik 673e572233 Use challenge straight away if available 2020-12-04 10:20:42 +02:00
Pavel Djundik 7c8e5add77 Support challenges in A2S_INFO 2020-12-04 10:18:46 +02:00
Pavel Djundik fbabd440da Fix variable 2020-12-04 10:14:45 +02:00
Pavel Djundik bbb6c4c23e Revert "Add 1200 byte padding to all requests"
This reverts commit 7231921b0b.
2020-12-04 10:05:37 +02:00
Pavel Djundik 9da781a993 Remove old badges 2020-12-03 13:01:53 +02:00
Pavel Djundik b4601cdf86 Print info if some was received 2020-12-03 12:51:26 +02:00
Pavel Djundik 7231921b0b Add 1200 byte padding to all requests
https://steamcommunity.com/discussions/forum/14/2989789048633291344/
2020-11-19 10:58:23 +02:00
Pavel Djundik 4f88fb0c76 Use actual class 2020-06-23 20:28:39 +03:00
Pavel Djundik 7414334804 Require php 7.4 2020-06-23 20:22:17 +03:00
Pavel Djundik 8a2c16123c Test php 7.4 2020-06-23 20:21:19 +03:00
Pavel Djundik 528d0ef4fc Update phpunit, add psalm 2020-06-23 20:20:41 +03:00
Pavel Djundik 914ec4b79a Add more type info 2020-06-23 20:10:31 +03:00
Pavel Djundik b87c9faab9 Fix up some type information 2020-06-23 19:44:51 +03:00
Pavel Djundik 1078e47b7e Add missing uses 2020-06-23 19:36:17 +03:00
Pavel Djundik e00235c29a Add S2A extra data comments 2020-06-19 17:43:51 +03:00
Pavel Djundik 75d3465690 Drop the email 2019-11-25 16:07:21 +02:00
Pavel Djundik a92e4e4a34 Merge pull request #146 from zavg/master
Add 7 Days to Die to supported games
2019-11-11 16:01:23 +02:00
zavg f22c5494d2 Update README.md 2019-11-11 16:56:49 +03:00
Pavel Djundik d9c72e198f Move to github actions 2019-11-04 13:56:29 +02:00
Pavel Djundik 84a18a4190 Verbose php-coveralls 2019-04-24 10:47:17 +03:00
Pavel Djundik d801629834 Specify php runtime 2019-04-24 10:44:21 +03:00
Pavel Djundik 724f047a4a Use coveralls for test coverage 2019-04-24 10:41:33 +03:00
Pavel Djundik d36c10a326 Update travis badge 2018-10-26 15:09:11 +03:00
Pavel Djundik f713415696 Update README.md 2018-03-19 21:10:59 +02:00
Pavel Djundik 6ad9f30ea4 Update phpunit.xml 2018-03-19 21:09:12 +02:00
Pavel Djundik 5597c4c17f Merge pull request #126 from Mechtecs/patch-1 2018-01-17 23:52:34 +02:00
Max Schorradt 75af0dba44 Correctly sorted the game app ids 2018-01-17 22:48:14 +01:00
Pavel Djundik bb3bb3e626 Run vendor phpunit 2017-12-27 20:23:36 +02:00
Pavel Djundik 2b5724c479 Change tested php versions 2017-12-26 18:39:08 +02:00
Pavel Djundik bf4a8a601a Add needed CS:GO cvars to readme 2017-12-26 18:31:10 +02:00
Pavel Djundik 5b39d65038 Merge pull request #124 from InvexByte/master
Fix #43 - Only write SERVERDATA_RESPONSE_VALUE once for multi packet responses
2017-12-26 18:27:28 +02:00
Byte ee60bef189 Only write SERVERDATA_RESPONSE_VALUE once for multi packet responses
This fixes the bug where all RCON queries on same SourceQuery object would return empty responses
2017-12-27 03:23:43 +11:00
Pavel Djundik 95057e9c35 Fix #121 2017-10-24 20:56:14 +03:00
Pavel Djundik b9861f3c97 Mark Rust RCON as unsupported 2017-03-25 11:09:15 +02:00
Pavel Djundik 6d60bb1d03 Remove dot from phpunit.xml name 2016-05-14 18:49:08 +03:00
Pavel Djundik d4ae8c631d Fix gold source query not working
Closes #95
2016-05-14 18:39:05 +03:00
Thomas Jones ccda8686ae Add Quake Live game (#94) 2016-04-13 10:14:47 +03:00
Pavel Djundik 0157fe89b6 Update README.md 2016-02-10 14:59:42 +02:00
Pavel Djundik 09a8af4352 Update CONTRIBUTING.md 2016-02-10 14:58:05 +02:00
Pavel Djundik 3959cda6e3 Create CONTRIBUTING.md 2016-02-10 14:55:57 +02:00
Pavel Djundik faf138591d Rust no longer requires +1 in rcon
Fixes #88
2016-02-08 16:50:46 +02:00
Pavel Djundik 205ad2ec97 Send initial challenge request as 4 byte int
Closes #79
2015-12-15 15:40:19 +02:00
Pavel Djundik 6d1de4182f Missed quote 2015-12-12 17:22:52 +02:00
Pavel Djundik dd9cc14ba7 Display players/rules count for easier debugging 2015-12-12 17:22:31 +02:00
Pavel Djundik 3e572da580 Test PHP 7.0 2015-12-05 16:18:30 +02:00
Pavel Djundik 9662c507b8 Space Engineers works
Closes #78
2015-12-02 12:12:50 +02:00
Pavel Djundik ce32b661d5 Put badges on a separate line 2015-10-28 19:08:02 +02:00
Pavel Djundik 4073003d27 Don't use consts 2015-10-24 22:50:31 +03:00
Pavel Djundik d6ce30970e Don't use consts and call GetRules 2015-10-24 22:48:02 +03:00
Pavel Djundik 85dd439369 Challenge reply should be S2A_CHALLENGE 2015-10-24 22:42:45 +03:00
Pavel Djundik 082d7bb690 Remove argument 2015-10-24 22:39:23 +03:00
Pavel Djundik 65f7653469 Test challenge caching 2015-10-24 22:37:57 +03:00
Pavel Djundik 499f4d7afe Test bad players/rules packets after correct challenge 2015-10-24 22:32:51 +03:00
Pavel Djundik ea95b515e5 Test players and rules for bad packets 2015-10-24 22:19:25 +03:00
Pavel Djundik 8642079233 Add RCON non connected tests 2015-10-24 16:21:01 +03:00
Pavel Djundik 613299b2bc Throw more when using RCON 2015-10-24 16:11:55 +03:00
Pavel Djundik 4be6bf48c1 Simplify challenges 2015-10-24 16:08:34 +03:00
Pavel Djundik d322b3bb78 Add players tests and some goldsource info tests 2015-10-22 18:49:21 +03:00
Pavel Djundik 750df786ec Everyone needs a shield 2015-10-22 13:40:08 +03:00
Pavel Djundik 19b63dff79 Correct variable 2015-10-22 13:33:51 +03:00
Pavel Djundik 8a8f1585cf Add rules test 2015-10-22 13:31:24 +03:00
Pavel Djundik 98749925c7 Pass length into ReadInternal 2015-10-22 12:24:34 +03:00
Pavel Djundik 2bbe339458 Rename some variables for clarity 2015-10-22 11:58:33 +03:00
Pavel Djundik 8b475d2649 Add timeout and not connected test 2015-10-22 00:31:45 +03:00
Pavel Djundik 4a73f14902 Make test mock a queue 2015-10-22 00:26:31 +03:00
Pavel Djundik c001fe6633 Remove unused reset function 2015-10-22 00:20:09 +03:00
Pavel Djundik 419f66ff72 And now make it work 2015-10-22 00:10:43 +03:00
Pavel Djundik 3c0ca191b5 Pass in callable correctly 2015-10-22 00:06:04 +03:00
Pavel Djundik 29597ff1b6 Add some GetInfo tests 2015-10-21 23:54:51 +03:00
Pavel Djundik af50a4a33a Throw in Buffer when there is not enough data to unpack 2015-10-21 23:54:14 +03:00
Pavel Djundik 5ab3873256 Improve socket mocking 2015-10-21 23:54:04 +03:00
Pavel Djundik a8d4418f28 Disable more camelcase goodness 2015-10-21 16:21:57 +03:00
Pavel Djundik 85a2dd534f Disable camelcase reporting 2015-10-21 16:08:32 +03:00
Pavel Djundik f9dc00ccf1 Make .codeclimate.sh executable 2015-10-21 16:03:06 +03:00
Pavel Djundik b767c63b7c Correct setup 2015-10-21 16:02:06 +03:00
Pavel Djundik a5f54674d0 Humble test beginnings 2015-10-21 15:56:38 +03:00
Pavel Djundik 6a94bcb6a6 Update .gitignore 2015-10-20 11:34:53 +03:00
Pavel Djundik 9c944e23d0 Make an abstract class for sockets and allow specifying different socket in SourceQuery
This is a preparation for tests
2015-10-19 22:18:10 +03:00
Pavel Djundik 412bad768c Fix module name and readme 2015-10-19 22:08:05 +03:00
Pavel Djundik 7636340092 Use gmp module to correctly read server's steamid on 32-bit systems 2015-10-19 22:04:34 +03:00
Pavel Djundik f41e4b551c Throw an exception when nothing was read from socket 2015-10-19 21:43:07 +03:00
Pavel Djundik d42aae5046 Get rid of global buffer
Closes #75
2015-10-19 21:38:05 +03:00
Pavel Djundik caf5296b5b Prevent tables overflowing 2015-10-19 21:38:05 +03:00
Pavel Djundik ef0af834c6 Add authors to composer.json 2015-10-19 13:15:35 +03:00
Pavel Djundik 24137d64e1 Failed spelling 2015-10-18 22:07:52 +03:00
Pavel Djundik 418dfd4050 Document additional throws and fix return 2015-10-18 22:06:52 +03:00
Pavel Djundik c74a5dcc99 Remove TimeoutException
Closes #74
2015-10-18 22:03:51 +03:00
Pavel Djundik 77e5af8745 Remove socket open check which can never be false 2015-10-18 22:01:28 +03:00
Pavel Djundik f18f3208fa Throw SocketException when trying to call query functions when not being connected 2015-10-18 21:56:34 +03:00
Pavel Djundik a63112c2c9 Bump minimum PHP version to 5.5 2015-10-18 21:46:39 +03:00
Pavel Djundik e798d8f7c2 Change heading sizes 2015-10-11 13:10:35 +03:00
Pavel Djundik b50daaef4c Add packagist version 2015-10-11 13:09:32 +03:00
Pavel Djundik dc2a08e503 Less spacing 2015-10-11 13:06:02 +03:00
Pavel Djundik 41741b24a2 Update bootstrap 2015-10-11 13:05:27 +03:00
Pavel Djundik 65a2493c3c Update github url 2015-10-11 12:56:56 +03:00
Pavel Djundik 1578c9f12f Correct license 2015-10-11 12:54:20 +03:00
Pavel Djundik 80cf750db5 Update README.md 2015-10-11 12:52:24 +03:00
Pavel Djundik 346151b51f Add extra keywords for supported games 2015-10-11 12:48:09 +03:00
Pavel Djundik 2591684d24 Create .editorconfig 2015-10-11 12:44:37 +03:00
Pavel Djundik e570cf796c Merge pull request #72 from timmw/master
PSR-4 support for #70 plus PhpDoc
2015-10-10 20:49:58 +03:00
Tim 9af9cf4f6b PSR-4 support for #70 plus PhpDoc 2015-10-10 18:48:52 +01:00
46 changed files with 2741 additions and 1348 deletions
+8
View File
@@ -0,0 +1,8 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = tab
insert_final_newline = true
+18
View File
@@ -0,0 +1,18 @@
name: CI
on: [push]
jobs:
php:
runs-on: ubuntu-latest
strategy:
matrix:
php: [7.4]
steps:
- uses: actions/checkout@v1
- name: Install dependencies
run: composer install --no-interaction --no-progress
- name: Run tests
run: php${{ matrix.php }} vendor/bin/phpunit --configuration Tests/phpunit.xml --verbose --fail-on-warning
- name: Run psalm
run: php${{ matrix.php }} vendor/bin/psalm
+2 -2
View File
@@ -1,2 +1,2 @@
/.idea/ vendor/
/vendor/ composer.lock
+2
View File
@@ -0,0 +1,2 @@
* **This library is intended for software developers**, if you do not know how write code in PHP, please do not create issues.
* Please do not create issues when you are unable to retrieve information from a server *(e.g. issues with UDP connectivity)*, unless you can prove that there is a bug within the library.
+8 -4
View File
@@ -1,5 +1,7 @@
<?php <?php
require __DIR__ . '/SourceQuery/SourceQuery.class.php'; require __DIR__ . '/../SourceQuery/bootstrap.php';
use xPaw\SourceQuery\SourceQuery;
// For the sake of this example // For the sake of this example
Header( 'Content-Type: text/plain' ); Header( 'Content-Type: text/plain' );
@@ -9,7 +11,7 @@
define( 'SQ_SERVER_ADDR', 'localhost' ); define( 'SQ_SERVER_ADDR', 'localhost' );
define( 'SQ_SERVER_PORT', 27015 ); define( 'SQ_SERVER_PORT', 27015 );
define( 'SQ_TIMEOUT', 1 ); define( 'SQ_TIMEOUT', 1 );
define( 'SQ_ENGINE', SourceQuery :: SOURCE ); define( 'SQ_ENGINE', SourceQuery::SOURCE );
// Edit this <- // Edit this <-
$Query = new SourceQuery( ); $Query = new SourceQuery( );
@@ -26,5 +28,7 @@
{ {
echo $e->getMessage( ); echo $e->getMessage( );
} }
finally
$Query->Disconnect( ); {
$Query->Disconnect( );
}
+8 -4
View File
@@ -1,5 +1,7 @@
<?php <?php
require __DIR__ . '/SourceQuery/SourceQuery.class.php'; require __DIR__ . '/../SourceQuery/bootstrap.php';
use xPaw\SourceQuery\SourceQuery;
// For the sake of this example // For the sake of this example
Header( 'Content-Type: text/plain' ); Header( 'Content-Type: text/plain' );
@@ -9,7 +11,7 @@
define( 'SQ_SERVER_ADDR', 'localhost' ); define( 'SQ_SERVER_ADDR', 'localhost' );
define( 'SQ_SERVER_PORT', 27015 ); define( 'SQ_SERVER_PORT', 27015 );
define( 'SQ_TIMEOUT', 1 ); define( 'SQ_TIMEOUT', 1 );
define( 'SQ_ENGINE', SourceQuery :: SOURCE ); define( 'SQ_ENGINE', SourceQuery::SOURCE );
// Edit this <- // Edit this <-
$Query = new SourceQuery( ); $Query = new SourceQuery( );
@@ -26,5 +28,7 @@
{ {
echo $e->getMessage( ); echo $e->getMessage( );
} }
finally
$Query->Disconnect( ); {
$Query->Disconnect( );
}
+46 -29
View File
@@ -1,11 +1,13 @@
<?php <?php
require __DIR__ . '/SourceQuery/SourceQuery.class.php'; require __DIR__ . '/../SourceQuery/bootstrap.php';
use xPaw\SourceQuery\SourceQuery;
// Edit this -> // Edit this ->
define( 'SQ_SERVER_ADDR', 'localhost' ); define( 'SQ_SERVER_ADDR', 'localhost' );
define( 'SQ_SERVER_PORT', 27015 ); define( 'SQ_SERVER_PORT', 27015 );
define( 'SQ_TIMEOUT', 3 ); define( 'SQ_TIMEOUT', 3 );
define( 'SQ_ENGINE', SourceQuery :: SOURCE ); define( 'SQ_ENGINE', SourceQuery::SOURCE );
// Edit this <- // Edit this <-
$Timer = MicroTime( true ); $Timer = MicroTime( true );
@@ -15,6 +17,7 @@
$Info = Array( ); $Info = Array( );
$Rules = Array( ); $Rules = Array( );
$Players = Array( ); $Players = Array( );
$Exception = null;
try try
{ {
@@ -29,8 +32,10 @@
{ {
$Exception = $e; $Exception = $e;
} }
finally
$Query->Disconnect( ); {
$Query->Disconnect( );
}
$Timer = Number_Format( MicroTime( true ) - $Timer, 4, '.', '' ); $Timer = Number_Format( MicroTime( true ) - $Timer, 4, '.', '' );
?> ?>
@@ -38,13 +43,17 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Source Query PHP Class</title> <title>Source Query PHP Library</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<style type="text/css"> <style type="text/css">
.jumbotron { .table {
margin-top: 30px; table-layout: fixed;
border-radius: 0; border-top-color: #428BCA;
}
.table td {
overflow-x: auto;
} }
.table thead th { .table thead th {
@@ -52,36 +61,45 @@
border-color: #428BCA !important; border-color: #428BCA !important;
color: #FFF; color: #FFF;
} }
.info-column {
width: 120px;
}
.frags-column {
width: 80px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="jumbotron">
<div class="jumbotron"> <div class="container">
<h1>Source Query PHP Class</h1> <h1>Source Query PHP Library</h1>
<p>This class was created to query game server which use the Source (Steamworks) query protocol.</p> <p class="lead">This library was created to query game server which use the Source (Steamworks) query protocol.</p>
<p> <p>
<a class="btn btn-large btn-primary" href="http://xpaw.me">Made by xPaw</a> <a class="btn btn-large btn-primary" href="https://xpaw.me">Made by xPaw</a>
<a class="btn btn-large btn-primary" href="https://github.com/xPaw/PHP-Source-Query-Class">View on GitHub</a> <a class="btn btn-large btn-primary" href="https://github.com/xPaw/PHP-Source-Query">View on GitHub</a>
<a class="btn btn-large btn-danger" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">CC BY-NC-SA 3.0</a> <a class="btn btn-large btn-danger" href="https://github.com/xPaw/PHP-Source-Query/blob/master/LICENSE">LGPL v2.1</a>
</p> </p>
</div> </div>
</div>
<?php if( isset( $Exception ) ): ?> <div class="container">
<div class="panel panel-primary"> <?php if( $Exception !== null ): ?>
<div class="panel-heading"><?php echo Get_Class( $Exception ); ?> at line <?php echo $Exception->getLine( ); ?></div> <div class="panel panel-error">
<p><b><?php echo htmlspecialchars( $Exception->getMessage( ) ); ?></b></p> <pre class="panel-body"><?php echo htmlspecialchars( $Exception->__toString( ) ); ?></pre>
<p><?php echo nl2br( $e->getTraceAsString(), false ); ?></p>
</div> </div>
<?php else: ?> <?php endif; ?>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
<table class="table table-bordered table-striped"> <table class="table table-bordered table-striped">
<thead> <thead>
<tr> <tr>
<th colspan="2">Server Info <span class="label label-<?php echo $Timer > 1.0 ? 'danger' : 'success'; ?>"><?php echo $Timer; ?>s</span></th> <th class="info-column">Server Info</th>
<th><span class="label label-<?php echo $Timer > 1.0 ? 'danger' : 'success'; ?>"><?php echo $Timer; ?>s</span></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -126,13 +144,13 @@
<table class="table table-bordered table-striped"> <table class="table table-bordered table-striped">
<thead> <thead>
<tr> <tr>
<th>Player</th> <th>Player <span class="label label-info"><?php echo count( $Players ); ?></span></th>
<th>Frags</th> <th class="frags-column">Frags</th>
<th>Time</th> <th class="frags-column">Time</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php if( Is_Array( $Players ) ): ?> <?php if( !empty( $Players ) ): ?>
<?php foreach( $Players as $Player ): ?> <?php foreach( $Players as $Player ): ?>
<tr> <tr>
<td><?php echo htmlspecialchars( $Player[ 'Name' ] ); ?></td> <td><?php echo htmlspecialchars( $Player[ 'Name' ] ); ?></td>
@@ -154,7 +172,7 @@
<table class="table table-bordered table-striped"> <table class="table table-bordered table-striped">
<thead> <thead>
<tr> <tr>
<th colspan="2">Rules</th> <th colspan="2">Rules <span class="label label-info"><?php echo count( $Rules ); ?></span></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -174,7 +192,6 @@
</table> </table>
</div> </div>
</div> </div>
<?php endif; ?>
</div> </div>
</body> </body>
</html> </html>
+18 -9
View File
@@ -1,4 +1,7 @@
# PHP Source Query [![Packagist](https://img.shields.io/packagist/dt/xpaw/php-source-query-class.svg)](https://packagist.org/packages/xpaw/php-source-query-class) # PHP Source Query
[![Packagist Downloads](https://img.shields.io/packagist/dt/xpaw/php-source-query-class.svg)](https://packagist.org/packages/xpaw/php-source-query-class)
[![Packagist Version](https://img.shields.io/packagist/v/xpaw/php-source-query-class.svg)](https://packagist.org/packages/xpaw/php-source-query-class)
This class was created to query game server which use the Source query protocol, this includes all source games, and all the games that implement Steamworks. This class was created to query game server which use the Source query protocol, this includes all source games, and all the games that implement Steamworks.
@@ -6,11 +9,14 @@ The class also allows you to query servers using RCON although this only works f
[Minecraft](http://www.minecraft.net) also uses Source RCON protocol, and this means you can use this class to send commands to your minecraft server while having engine set to Source engine. [Minecraft](http://www.minecraft.net) also uses Source RCON protocol, and this means you can use this class to send commands to your minecraft server while having engine set to Source engine.
**This library requires a [modern version](https://php.net/supported-versions.php) of PHP, so make sure you are using at least PHP 5.4 or newer.**
**:warning: Please do not create issues when you are unable to retrieve information from a server, unless you can prove that there is a bug within the library.** **:warning: Please do not create issues when you are unable to retrieve information from a server, unless you can prove that there is a bug within the library.**
### Protocol specifications can be found over at VDC ## Requirements
* [Modern PHP version](https://php.net/supported-versions.php) (7.4 or newer)
* 64-bit PHP or [gmp module](https://secure.php.net/manual/en/book.gmp.php)
* Web server must allow UDP connections
## Protocol Specifications
* https://developer.valvesoftware.com/wiki/Server_queries * https://developer.valvesoftware.com/wiki/Server_queries
* https://developer.valvesoftware.com/wiki/Source_RCON_Protocol * https://developer.valvesoftware.com/wiki/Source_RCON_Protocol
@@ -19,9 +25,9 @@ AppID | Game | Query | RCON | Notes
----- | ---- | :---: | :--: | ---- ----- | ---- | :---: | :--: | ----
~ | All HL1/HL2 games and mods | :white_check_mark: | :white_check_mark: | ~ | All HL1/HL2 games and mods | :white_check_mark: | :white_check_mark: |
10 | [Counter-Strike 1.6](http://store.steampowered.com/app/10/) | :white_check_mark: | :white_check_mark: | 10 | [Counter-Strike 1.6](http://store.steampowered.com/app/10/) | :white_check_mark: | :white_check_mark: |
730 | [Counter-Strike: Global Offensive](http://store.steampowered.com/app/730/) | :white_check_mark: | :white_check_mark: |
440 | [Team Fortress 2](http://store.steampowered.com/app/440/) | :white_check_mark: | :white_check_mark: | 440 | [Team Fortress 2](http://store.steampowered.com/app/440/) | :white_check_mark: | :white_check_mark: |
550 | [Left 4 Dead 2](http://store.steampowered.com/app/550/) | :white_check_mark: | :white_check_mark: | 550 | [Left 4 Dead 2](http://store.steampowered.com/app/550/) | :white_check_mark: | :white_check_mark: |
730 | [Counter-Strike: Global Offensive](http://store.steampowered.com/app/730/) | :white_check_mark: | :white_check_mark: | `host_name_store 1; host_info_show 2; host_players_show 2`
1002 | [Rag Doll Kung Fu](http://store.steampowered.com/app/1002/) | :white_check_mark: | :white_check_mark: | 1002 | [Rag Doll Kung Fu](http://store.steampowered.com/app/1002/) | :white_check_mark: | :white_check_mark: |
2400 | [The Ship](http://store.steampowered.com/app/2400/) | :white_check_mark: | :white_check_mark: | 2400 | [The Ship](http://store.steampowered.com/app/2400/) | :white_check_mark: | :white_check_mark: |
4000 | [Garry's Mod](http://store.steampowered.com/app/4000/) | :white_check_mark: | :white_check_mark: | 4000 | [Garry's Mod](http://store.steampowered.com/app/4000/) | :white_check_mark: | :white_check_mark: |
@@ -30,13 +36,16 @@ AppID | Game | Query | RCON | Notes
107410 | [Arma 3](http://store.steampowered.com/app/107410/) | :white_check_mark: | :x: | Add +1 to the server port 107410 | [Arma 3](http://store.steampowered.com/app/107410/) | :white_check_mark: | :x: | Add +1 to the server port
115300 | [Call of Duty: Modern Warfare 3](http://store.steampowered.com/app/115300/) | :white_check_mark: | :white_check_mark: | 115300 | [Call of Duty: Modern Warfare 3](http://store.steampowered.com/app/115300/) | :white_check_mark: | :white_check_mark: |
211820 | [Starbound](http://store.steampowered.com/app/211820/) | :white_check_mark: | :white_check_mark: | Call `SetUseOldGetChallengeMethod` method after connecting 211820 | [Starbound](http://store.steampowered.com/app/211820/) | :white_check_mark: | :white_check_mark: | Call `SetUseOldGetChallengeMethod` method after connecting
252490 | [Rust](http://store.steampowered.com/app/252490/) | :white_check_mark: | :white_check_mark: | Add +1 to the server port 244850 | [Space Engineers](http://store.steampowered.com/app/244850/) | :white_check_mark: | :x: | Add +1 to the server port
251570 | [7 Days to Die](http://store.steampowered.com/app/251570) | :white_check_mark: | :x: |
252490 | [Rust](http://store.steampowered.com/app/252490/) | :white_check_mark: | :x: |
282440 | [Quake Live](http://store.steampowered.com/app/282440) | :white_check_mark: | :x: | Quake Live uses the ZMQ messaging queue protocol for rcon control.
346110 | [ARK: Survival Evolved](http://store.steampowered.com/app/346110/) | :white_check_mark: | :white_check_mark: | 346110 | [ARK: Survival Evolved](http://store.steampowered.com/app/346110/) | :white_check_mark: | :white_check_mark: |
| [Minecraft](http://www.minecraft.net/) | :x: | :white_check_mark: | ~ | [Minecraft](http://www.minecraft.net/) | :x: | :white_check_mark: |
Open a pull request if you know another game which supports Source Query and/or RCON protocols. Open a pull request if you know another game which supports Source Query and/or RCON protocols.
# How to tell if the game supports Source Query Protocol? ## How to tell if the game supports Source Query Protocol?
Add your server to your favourites in Steam server browser, and if Steam can display information about your server, then the protocol is supported. Add your server to your favourites in Steam server browser, and if Steam can display information about your server, then the protocol is supported.
@@ -76,7 +85,7 @@ Add your server to your favourites in Steam server browser, and if Steam can dis
</tr> </tr>
</table> </table>
Also refer to [an example](Example.php) to work things out. Also refer to [examples folder](Examples/) to work things out.
## License ## License
PHP Source Query PHP Source Query
+139
View File
@@ -0,0 +1,139 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery;
use xPaw\SourceQuery\Exception\InvalidPacketException;
use xPaw\SourceQuery\Exception\SocketException;
/**
* Base socket interface
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
* @uses xPaw\SourceQuery\Exception\SocketException
*/
abstract class BaseSocket
{
/** @var resource */
public $Socket;
public int $Engine;
public string $Address;
public int $Port;
public int $Timeout;
public function __destruct( )
{
$this->Close( );
}
abstract public function Close( ) : void;
abstract public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void;
abstract public function Write( int $Header, string $String = '' ) : bool;
abstract public function Read( int $Length = 1400 ) : Buffer;
protected function ReadInternal( Buffer $Buffer, int $Length, callable $SherlockFunction ) : Buffer
{
if( $Buffer->Remaining( ) === 0 )
{
throw new InvalidPacketException( 'Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
}
$Header = $Buffer->GetLong( );
if( $Header === -1 ) // Single packet
{
// We don't have to do anything
}
else if( $Header === -2 ) // Split packet
{
$Packets = [];
$IsCompressed = false;
$ReadMore = false;
$PacketChecksum = null;
do
{
$RequestID = $Buffer->GetLong( );
switch( $this->Engine )
{
case SourceQuery::GOLDSOURCE:
{
$PacketCountAndNumber = $Buffer->GetByte( );
$PacketCount = $PacketCountAndNumber & 0xF;
$PacketNumber = $PacketCountAndNumber >> 4;
break;
}
case SourceQuery::SOURCE:
{
$IsCompressed = ( $RequestID & 0x80000000 ) !== 0;
$PacketCount = $Buffer->GetByte( );
$PacketNumber = $Buffer->GetByte( ) + 1;
if( $IsCompressed )
{
$Buffer->GetLong( ); // Split size
$PacketChecksum = $Buffer->GetUnsignedLong( );
}
else
{
$Buffer->GetShort( ); // Split size
}
break;
}
default:
{
throw new SocketException( 'Unknown engine.', SocketException::INVALID_ENGINE );
}
}
$Packets[ $PacketNumber ] = $Buffer->Get( );
$ReadMore = $PacketCount > sizeof( $Packets );
}
while( $ReadMore && $SherlockFunction( $Buffer, $Length ) );
$Data = Implode( $Packets );
// TODO: Test this
if( $IsCompressed )
{
// Let's make sure this function exists, it's not included in PHP by default
if( !Function_Exists( 'bzdecompress' ) )
{
throw new \RuntimeException( 'Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.' );
}
$Data = bzdecompress( $Data );
if( !is_string( $Data ) || CRC32( $Data ) !== $PacketChecksum )
{
throw new InvalidPacketException( 'CRC32 checksum mismatch of uncompressed packet data.', InvalidPacketException::CHECKSUM_MISMATCH );
}
}
$Buffer->Set( SubStr( $Data, 4 ) );
}
else
{
throw new InvalidPacketException( 'Socket read: Raw packet header mismatch. (0x' . DecHex( $Header ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
return $Buffer;
}
}
-176
View File
@@ -1,176 +0,0 @@
<?php
/**
* Class written by xPaw
*
* Website: https://xpaw.me
* GitHub: https://github.com/xPaw/PHP-Source-Query-Class
*/
class SourceQueryBuffer
{
/**
* Buffer
*
* @var string
*/
private $Buffer;
/**
* Buffer length
*
* @var int
*/
private $Length;
/**
* Current position in buffer
*
* @var int
*/
private $Position;
/**
* Sets buffer
*
* @param string $Buffer Buffer
*/
public function Set( $Buffer )
{
$this->Buffer = $Buffer;
$this->Length = StrLen( $Buffer );
$this->Position = 0;
}
/**
* Resets buffer
*/
public function Reset( )
{
$this->Buffer = "";
$this->Length = 0;
$this->Position = 0;
}
/**
* Get remaining bytes
*
* @return int Remaining bytes in buffer
*/
public function Remaining( )
{
return $this->Length - $this->Position;
}
/**
* Gets data from buffer
*
* @param int $Length Bytes to read
*
* @return string
*/
public function Get( $Length = -1 )
{
if( $Length === 0 )
{
return '';
}
$Remaining = $this->Remaining( );
if( $Length === -1 )
{
$Length = $Remaining;
}
else if( $Length > $Remaining )
{
return '';
}
$Data = SubStr( $this->Buffer, $this->Position, $Length );
$this->Position += $Length;
return $Data;
}
/**
* Get byte from buffer
*
* @return int
*/
public function GetByte( )
{
return Ord( $this->Get( 1 ) );
}
/**
* Get short from buffer
*
* @return int
*/
public function GetShort( )
{
$Data = UnPack( 'v', $this->Get( 2 ) );
return $Data[ 1 ];
}
/**
* Get long from buffer
*
* @return int
*/
public function GetLong( )
{
$Data = UnPack( 'l', $this->Get( 4 ) );
return $Data[ 1 ];
}
/**
* Get float from buffer
*
* @return float
*/
public function GetFloat( )
{
$Data = UnPack( 'f', $this->Get( 4 ) );
return $Data[ 1 ];
}
/**
* Get unsigned long from buffer
*
* @return int
*/
public function GetUnsignedLong( )
{
$Data = UnPack( 'V', $this->Get( 4 ) );
return $Data[ 1 ];
}
/**
* Read one string from buffer ending with null byte
*
* @return string
*/
public function GetString( )
{
$ZeroBytePosition = StrPos( $this->Buffer, "\0", $this->Position );
if( $ZeroBytePosition === false )
{
$String = "";
}
else
{
$String = $this->Get( $ZeroBytePosition - $this->Position );
$this->Position++;
}
return $String;
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery;
use xPaw\SourceQuery\Exception\InvalidPacketException;
/**
* Class Buffer
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
*/
class Buffer
{
/**
* Buffer
*/
private string $Buffer = '';
/**
* Buffer length
*/
private int $Length = 0;
/**
* Current position in buffer
*/
private int $Position = 0;
/**
* Sets buffer
*/
public function Set( string $Buffer ) : void
{
$this->Buffer = $Buffer;
$this->Length = StrLen( $Buffer );
$this->Position = 0;
}
/**
* Get remaining bytes
*
* @return int Remaining bytes in buffer
*/
public function Remaining( ) : int
{
return $this->Length - $this->Position;
}
/**
* Gets data from buffer
*
* @param int $Length Bytes to read
*/
public function Get( int $Length = -1 ) : string
{
if( $Length === 0 )
{
return '';
}
$Remaining = $this->Remaining( );
if( $Length === -1 )
{
$Length = $Remaining;
}
else if( $Length > $Remaining )
{
return '';
}
$Data = SubStr( $this->Buffer, $this->Position, $Length );
$this->Position += $Length;
return $Data;
}
/**
* Get byte from buffer
*/
public function GetByte( ) : int
{
return Ord( $this->Get( 1 ) );
}
/**
* Get short from buffer
*/
public function GetShort( ) : int
{
if( $this->Remaining( ) < 2 )
{
throw new InvalidPacketException( 'Not enough data to unpack a short.', InvalidPacketException::BUFFER_EMPTY );
}
$Data = UnPack( 'v', $this->Get( 2 ) );
return (int)$Data[ 1 ];
}
/**
* Get long from buffer
*/
public function GetLong( ) : int
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack a long.', InvalidPacketException::BUFFER_EMPTY );
}
$Data = UnPack( 'l', $this->Get( 4 ) );
return (int)$Data[ 1 ];
}
/**
* Get float from buffer
*/
public function GetFloat( ) : float
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack a float.', InvalidPacketException::BUFFER_EMPTY );
}
$Data = UnPack( 'f', $this->Get( 4 ) );
return (float)$Data[ 1 ];
}
/**
* Get unsigned long from buffer
*/
public function GetUnsignedLong( ) : int
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack an usigned long.', InvalidPacketException::BUFFER_EMPTY );
}
$Data = UnPack( 'V', $this->Get( 4 ) );
return (int)$Data[ 1 ];
}
/**
* Read one string from buffer ending with null byte
*/
public function GetString( ) : string
{
$ZeroBytePosition = StrPos( $this->Buffer, "\0", $this->Position );
if( $ZeroBytePosition === false )
{
return '';
}
$String = $this->Get( $ZeroBytePosition - $this->Position );
$this->Position++;
return $String;
}
}
@@ -0,0 +1,19 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery\Exception;
class AuthenticationException extends SourceQueryException
{
const BAD_PASSWORD = 1;
const BANNED = 2;
}
@@ -0,0 +1,18 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery\Exception;
class InvalidArgumentException extends SourceQueryException
{
const TIMEOUT_NOT_INTEGER = 1;
}
@@ -0,0 +1,21 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery\Exception;
class InvalidPacketException extends SourceQueryException
{
const PACKET_HEADER_MISMATCH = 1;
const BUFFER_EMPTY = 2;
const BUFFER_NOT_EMPTY = 3;
const CHECKSUM_MISMATCH = 4;
}
+21
View File
@@ -0,0 +1,21 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery\Exception;
class SocketException extends SourceQueryException
{
const COULD_NOT_CREATE_SOCKET = 1;
const NOT_CONNECTED = 2;
const CONNECTION_FAILED = 3;
const INVALID_ENGINE = 3;
}
@@ -0,0 +1,18 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery\Exception;
abstract class SourceQueryException extends \Exception
{
// Base exception class
}
-43
View File
@@ -1,43 +0,0 @@
<?php
/**
* Class written by xPaw
*
* Website: https://xpaw.me
* GitHub: https://github.com/xPaw/PHP-Source-Query-Class
*/
namespace xPaw\SourceQuery\Exception;
abstract class SourceQueryException extends \Exception
{
// Base exception class
}
class InvalidArgumentException extends SourceQueryException
{
const TIMEOUT_NOT_INTEGER = 1;
}
class TimeoutException extends SourceQueryException
{
const TIMEOUT_CONNECT = 1;
}
class InvalidPacketException extends SourceQueryException
{
const PACKET_HEADER_MISMATCH = 1;
const BUFFER_EMPTY = 2;
const BUFFER_NOT_EMPTY = 3;
const CHECKSUM_MISMATCH = 4;
}
class AuthenticationException extends SourceQueryException
{
const BAD_PASSWORD = 1;
const BANNED = 2;
}
class SocketException extends SourceQueryException
{
const COULD_NOT_CREATE_SOCKET = 1;
}
-136
View File
@@ -1,136 +0,0 @@
<?php
/**
* Class written by xPaw
*
* Website: https://xpaw.me
* GitHub: https://github.com/xPaw/PHP-Source-Query-Class
*/
use xPaw\SourceQuery\Exception\AuthenticationException;
class SourceQueryGoldSourceRcon
{
/**
* Points to buffer class
*
* @var SourceQueryBuffer
*/
private $Buffer;
/**
* Points to socket class
*
* @var SourceQuerySocket
*/
private $Socket;
private $RconPassword;
private $RconRequestId;
private $RconChallenge;
public function __construct( $Buffer, $Socket )
{
$this->Buffer = $Buffer;
$this->Socket = $Socket;
}
public function Close( )
{
$this->RconChallenge = 0;
$this->RconRequestId = 0;
$this->RconPassword = 0;
}
public function Open( )
{
//
}
public function Write( $Header, $String = '' )
{
$Command = Pack( 'cccca*', 0xFF, 0xFF, 0xFF, 0xFF, $String );
$Length = StrLen( $Command );
return $Length === FWrite( $this->Socket->Socket, $Command, $Length );
}
/**
* @param int $Length
* @throws AuthenticationException
*/
public function Read( $Length = 1400 )
{
// GoldSource RCON has same structure as Query
$this->Socket->Read( );
if( $this->Buffer->GetByte( ) !== SourceQuery :: S2A_RCON )
{
return false;
}
$Buffer = $this->Buffer->Get( );
$Trimmed = Trim( $Buffer );
if( $Trimmed === 'Bad rcon_password.' )
{
throw new AuthenticationException( $Trimmed, AuthenticationException::BAD_PASSWORD );
}
else if( $Trimmed === 'You have been banned from this server.' )
{
throw new AuthenticationException( $Trimmed, AuthenticationException::BANNED );
}
$ReadMore = false;
// There is no indentifier of the end, so we just need to continue reading
// TODO: Needs to be looked again, it causes timeouts
do
{
$this->Socket->Read( );
$ReadMore = $this->Buffer->Remaining( ) > 0 && $this->Buffer->GetByte( ) === SourceQuery :: S2A_RCON;
if( $ReadMore )
{
$Packet = $this->Buffer->Get( );
$Buffer .= SubStr( $Packet, 0, -2 );
// Let's assume if this packet is not long enough, there are no more after this one
$ReadMore = StrLen( $Packet ) > 1000; // use 1300?
}
}
while( $ReadMore );
$this->Buffer->Set( Trim( $Buffer ) );
}
public function Command( $Command )
{
if( !$this->RconChallenge )
{
return false;
}
$this->Write( 0, 'rcon ' . $this->RconChallenge . ' "' . $this->RconPassword . '" ' . $Command . "\0" );
$this->Read( );
return $this->Buffer->Get( );
}
public function Authorize( $Password )
{
$this->RconPassword = $Password;
$this->Write( 0, 'challenge rcon' );
$this->Socket->Read( );
if( $this->Buffer->Get( 14 ) !== 'challenge rcon' )
{
return false;
}
$this->RconChallenge = Trim( $this->Buffer->Get( ) );
return true;
}
}
+145
View File
@@ -0,0 +1,145 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery;
use xPaw\SourceQuery\Exception\AuthenticationException;
use xPaw\SourceQuery\Exception\InvalidPacketException;
/**
* Class GoldSourceRcon
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\AuthenticationException
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
*/
class GoldSourceRcon
{
/**
* Points to socket class
*
* @var BaseSocket
*/
private $Socket;
private string $RconPassword = '';
private string $RconChallenge = '';
public function __construct( BaseSocket $Socket )
{
$this->Socket = $Socket;
}
public function Close( ) : void
{
$this->RconChallenge = '';
$this->RconPassword = '';
}
public function Open( ) : void
{
//
}
public function Write( int $Header, string $String = '' ) : bool
{
$Command = Pack( 'cccca*', 0xFF, 0xFF, 0xFF, 0xFF, $String );
$Length = StrLen( $Command );
return $Length === FWrite( $this->Socket->Socket, $Command, $Length );
}
/**
* @param int $Length
* @throws AuthenticationException
* @return Buffer
*/
public function Read( int $Length = 1400 ) : Buffer
{
// GoldSource RCON has same structure as Query
$Buffer = $this->Socket->Read( );
$StringBuffer = '';
$ReadMore = false;
// There is no indentifier of the end, so we just need to continue reading
do
{
$ReadMore = $Buffer->Remaining( ) > 0;
if( $ReadMore )
{
if( $Buffer->GetByte( ) !== SourceQuery::S2A_RCON )
{
throw new InvalidPacketException( 'Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
$Packet = $Buffer->Get( );
$StringBuffer .= $Packet;
//$StringBuffer .= SubStr( $Packet, 0, -2 );
// Let's assume if this packet is not long enough, there are no more after this one
$ReadMore = StrLen( $Packet ) > 1000; // use 1300?
if( $ReadMore )
{
$Buffer = $this->Socket->Read( );
}
}
}
while( $ReadMore );
$Trimmed = trim( $StringBuffer );
if( $Trimmed === 'Bad rcon_password.' )
{
throw new AuthenticationException( $Trimmed, AuthenticationException::BAD_PASSWORD );
}
else if( $Trimmed === 'You have been banned from this server.' )
{
throw new AuthenticationException( $Trimmed, AuthenticationException::BANNED );
}
$Buffer->Set( $Trimmed );
return $Buffer;
}
public function Command( string $Command ) : string
{
if( !$this->RconChallenge )
{
throw new AuthenticationException( 'Tried to execute a RCON command before successful authorization.', AuthenticationException::BAD_PASSWORD );
}
$this->Write( 0, 'rcon ' . $this->RconChallenge . ' "' . $this->RconPassword . '" ' . $Command . "\0" );
$Buffer = $this->Read( );
return $Buffer->Get( );
}
public function Authorize( string $Password ) : void
{
$this->RconPassword = $Password;
$this->Write( 0, 'challenge rcon' );
$Buffer = $this->Socket->Read( );
if( $Buffer->Get( 14 ) !== 'challenge rcon' )
{
throw new AuthenticationException( 'Failed to get RCON challenge.', AuthenticationException::BAD_PASSWORD );
}
$this->RconChallenge = Trim( $Buffer->Get( ) );
}
}
-179
View File
@@ -1,179 +0,0 @@
<?php
/**
* Class written by xPaw
*
* Website: https://xpaw.me
* GitHub: https://github.com/xPaw/PHP-Source-Query-Class
*/
use xPaw\SourceQuery\Exception\InvalidPacketException;
use xPaw\SourceQuery\Exception\SocketException;
class SourceQuerySocket
{
public $Socket;
public $Engine;
public $Ip;
public $Port;
public $Timeout;
/**
* Points to buffer class
*
* @var SourceQueryBuffer
*/
private $Buffer;
public function __construct( $Buffer )
{
$this->Buffer = $Buffer;
}
public function Close( )
{
if( $this->Socket )
{
FClose( $this->Socket );
$this->Socket = null;
}
}
public function Open( $Ip, $Port, $Timeout, $Engine )
{
$this->Timeout = $Timeout;
$this->Engine = $Engine;
$this->Port = $Port;
$this->Ip = $Ip;
$this->Socket = @FSockOpen( 'udp://' . $Ip, $Port, $ErrNo, $ErrStr, $Timeout );
if( $ErrNo || $this->Socket === false )
{
throw new SocketException( 'Could not create socket: ' . $ErrStr, SocketException::COULD_NOT_CREATE_SOCKET );
}
Stream_Set_Timeout( $this->Socket, $Timeout );
Stream_Set_Blocking( $this->Socket, true );
return true;
}
public function Write( $Header, $String = '' )
{
$Command = Pack( 'ccccca*', 0xFF, 0xFF, 0xFF, 0xFF, $Header, $String );
$Length = StrLen( $Command );
return $Length === FWrite( $this->Socket, $Command, $Length );
}
public function Read( $Length = 1400 )
{
$this->ReadBuffer( FRead( $this->Socket, $Length ), $Length );
}
protected function ReadBuffer( $Buffer, $Length )
{
$this->Buffer->Set( $Buffer );
if( $this->Buffer->Remaining( ) === 0 )
{
// TODO: Should we throw an exception here?
return;
}
$Header = $this->Buffer->GetLong( );
if( $Header === -1 ) // Single packet
{
// We don't have to do anything
}
else if( $Header === -2 ) // Split packet
{
$Packets = Array( );
$IsCompressed = false;
$ReadMore = false;
do
{
$RequestID = $this->Buffer->GetLong( );
switch( $this->Engine )
{
case SourceQuery :: GOLDSOURCE:
{
$PacketCountAndNumber = $this->Buffer->GetByte( );
$PacketCount = $PacketCountAndNumber & 0xF;
$PacketNumber = $PacketCountAndNumber >> 4;
break;
}
case SourceQuery :: SOURCE:
{
$IsCompressed = ( $RequestID & 0x80000000 ) !== 0;
$PacketCount = $this->Buffer->GetByte( );
$PacketNumber = $this->Buffer->GetByte( ) + 1;
if( $IsCompressed )
{
$this->Buffer->GetLong( ); // Split size
$PacketChecksum = $this->Buffer->GetUnsignedLong( );
}
else
{
$this->Buffer->GetShort( ); // Split size
}
break;
}
}
$Packets[ $PacketNumber ] = $this->Buffer->Get( );
$ReadMore = $PacketCount > sizeof( $Packets );
}
while( $ReadMore && $this->Sherlock( $Length ) );
$Buffer = Implode( $Packets );
// TODO: Test this
if( $IsCompressed )
{
// Let's make sure this function exists, it's not included in PHP by default
if( !Function_Exists( 'bzdecompress' ) )
{
throw new RuntimeException( 'Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.' );
}
$Buffer = bzdecompress( $Buffer );
if( CRC32( $Buffer ) !== $PacketChecksum )
{
throw new InvalidPacketException( 'CRC32 checksum mismatch of uncompressed packet data.', InvalidPacketException::CHECKSUM_MISMATCH );
}
}
$this->Buffer->Set( SubStr( $Buffer, 4 ) );
}
else
{
throw new InvalidPacketException( 'Socket read: Raw packet header mismatch. (0x' . DecHex( $Header ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
}
private function Sherlock( $Length )
{
$Data = FRead( $this->Socket, $Length );
if( StrLen( $Data ) < 4 )
{
return false;
}
$this->Buffer->Set( $Data );
return $this->Buffer->GetLong( ) === -2;
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery;
use xPaw\SourceQuery\Exception\InvalidPacketException;
use xPaw\SourceQuery\Exception\SocketException;
/**
* Class Socket
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
* @uses xPaw\SourceQuery\Exception\SocketException
*/
class Socket extends BaseSocket
{
public function Close( ) : void
{
if( $this->Socket !== null )
{
FClose( $this->Socket );
$this->Socket = null;
}
}
public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void
{
$this->Timeout = $Timeout;
$this->Engine = $Engine;
$this->Port = $Port;
$this->Address = $Address;
$this->Socket = @FSockOpen( 'udp://' . $Address, $Port, $ErrNo, $ErrStr, $Timeout );
if( $ErrNo || $this->Socket === false )
{
throw new SocketException( 'Could not create socket: ' . $ErrStr, SocketException::COULD_NOT_CREATE_SOCKET );
}
Stream_Set_Timeout( $this->Socket, $Timeout );
Stream_Set_Blocking( $this->Socket, true );
}
public function Write( int $Header, string $String = '' ) : bool
{
$Command = Pack( 'ccccca*', 0xFF, 0xFF, 0xFF, 0xFF, $Header, $String );
$Length = StrLen( $Command );
return $Length === FWrite( $this->Socket, $Command, $Length );
}
/**
* Reads from socket and returns Buffer.
*
* @throws InvalidPacketException
*
* @return Buffer Buffer
*/
public function Read( int $Length = 1400 ) : Buffer
{
$Buffer = new Buffer( );
$Buffer->Set( FRead( $this->Socket, $Length ) );
$this->ReadInternal( $Buffer, $Length, [ $this, 'Sherlock' ] );
return $Buffer;
}
public function Sherlock( Buffer $Buffer, int $Length ) : bool
{
$Data = FRead( $this->Socket, $Length );
if( StrLen( $Data ) < 4 )
{
return false;
}
$Buffer->Set( $Data );
return $Buffer->GetLong( ) === -2;
}
}
-566
View File
@@ -1,566 +0,0 @@
<?php
/**
* Class written by xPaw
*
* Website: https://xpaw.me
* GitHub: https://github.com/xPaw/PHP-Source-Query-Class
*
* Special thanks to koraktor for his awesome Steam Condenser class,
* I used it as a reference at some points.
*/
require __DIR__ . '/Exceptions.class.php';
require __DIR__ . '/Buffer.class.php';
require __DIR__ . '/Socket.class.php';
require __DIR__ . '/SourceRcon.class.php';
require __DIR__ . '/GoldSourceRcon.class.php';
use xPaw\SourceQuery\Exception\InvalidArgumentException;
use xPaw\SourceQuery\Exception\TimeoutException;
use xPaw\SourceQuery\Exception\InvalidPacketException;
class SourceQuery
{
/**
* Values returned by GetChallenge()
*
* TODO: Get rid of this? Improve? Do something else?
*/
const GETCHALLENGE_FAILED = 0;
const GETCHALLENGE_ALL_CLEAR = 1;
const GETCHALLENGE_CONTAINS_ANSWER = 2;
/**
* Engines
*/
const GOLDSOURCE = 0;
const SOURCE = 1;
/**
* Packets sent
*/
const A2S_PING = 0x69;
const A2S_INFO = 0x54;
const A2S_PLAYER = 0x55;
const A2S_RULES = 0x56;
const A2S_SERVERQUERY_GETCHALLENGE = 0x57;
/**
* Packets received
*/
const S2A_PING = 0x6A;
const S2A_CHALLENGE = 0x41;
const S2A_INFO = 0x49;
const S2A_INFO_OLD = 0x6D; // Old GoldSource, HLTV uses it
const S2A_PLAYER = 0x44;
const S2A_RULES = 0x45;
const S2A_RCON = 0x6C;
/**
* Source rcon sent
*/
const SERVERDATA_EXECCOMMAND = 2;
const SERVERDATA_AUTH = 3;
/**
* Source rcon received
*/
const SERVERDATA_RESPONSE_VALUE = 0;
const SERVERDATA_AUTH_RESPONSE = 2;
/**
* Points to rcon class
*
* @var SourceQueryRcon
*/
private $Rcon;
/**
* Points to buffer class
*
* @var SourceQueryBuffer
*/
private $Buffer;
/**
* Points to socket class
*
* @var SourceQuerySocket
*/
private $Socket;
/**
* True if connection is open, false if not
*
* @var bool
*/
private $Connected;
/**
* Contains challenge
*
* @var string
*/
private $Challenge;
/**
* Use old method for getting challenge number
*
* @var bool
*/
private $UseOldGetChallengeMethod;
public function __construct( )
{
$this->Buffer = new SourceQueryBuffer( );
$this->Socket = new SourceQuerySocket( $this->Buffer );
}
public function __destruct( )
{
$this->Disconnect( );
}
/**
* Opens connection to server
*
* @param string $Ip Server ip
* @param int $Port Server port
* @param int $Timeout Timeout period
* @param int $Engine Engine the server runs on (goldsource, source)
*
* @throws InvalidArgumentException
* @throws TimeoutException
*/
public function Connect( $Ip, $Port, $Timeout = 3, $Engine = self :: SOURCE )
{
$this->Disconnect( );
if( !is_int( $Timeout ) || $Timeout < 0 )
{
throw new InvalidArgumentException( 'Timeout must be an integer.', InvalidArgumentException::TIMEOUT_NOT_INTEGER );
}
if( !$this->Socket->Open( $Ip, (int)$Port, $Timeout, (int)$Engine ) )
{
throw new TimeoutException( 'Could not connect to server.', TimeoutException::TIMEOUT_CONNECT );
}
$this->Connected = true;
}
/**
* Forces GetChallenge to use old method for challenge retrieval because some games use outdated protocol (e.g Starbound)
*
* @param bool $Value Set to true to force old method
*
* @returns bool Previous value
*/
public function SetUseOldGetChallengeMethod( $Value )
{
$Previous = $this->UseOldGetChallengeMethod;
$this->UseOldGetChallengeMethod = $Value === true;
return $Previous;
}
/**
* Closes all open connections
*/
public function Disconnect( )
{
$this->Connected = false;
$this->Challenge = 0;
$this->Buffer->Reset( );
$this->Socket->Close( );
if( $this->Rcon )
{
$this->Rcon->Close( );
$this->Rcon = null;
}
}
/**
* Sends ping packet to the server
* NOTE: This may not work on some games (TF2 for example)
*
* @return bool True on success, false on failure
*/
public function Ping( )
{
if( !$this->Connected )
{
return false;
}
$this->Socket->Write( self :: A2S_PING );
$this->Socket->Read( );
return $this->Buffer->GetByte( ) === self :: S2A_PING;
}
/**
* Get server information
*
* @throws InvalidPacketException
*
* @return bool|array Returns array with information on success, false on failure
*/
public function GetInfo( )
{
if( !$this->Connected )
{
return false;
}
$this->Socket->Write( self :: A2S_INFO, "Source Engine Query\0" );
$this->Socket->Read( );
$Type = $this->Buffer->GetByte( );
if( $Type === 0 )
{
return false;
}
// Old GoldSource protocol, HLTV still uses it
if( $Type === self :: S2A_INFO_OLD && $this->Socket->Engine === self :: GOLDSOURCE )
{
/**
* If we try to read data again, and we get the result with type S2A_INFO (0x49)
* That means this server is running dproto,
* Because it sends answer for both protocols
*/
$Server[ 'Address' ] = $this->Buffer->GetString( );
$Server[ 'HostName' ] = $this->Buffer->GetString( );
$Server[ 'Map' ] = $this->Buffer->GetString( );
$Server[ 'ModDir' ] = $this->Buffer->GetString( );
$Server[ 'ModDesc' ] = $this->Buffer->GetString( );
$Server[ 'Players' ] = $this->Buffer->GetByte( );
$Server[ 'MaxPlayers' ] = $this->Buffer->GetByte( );
$Server[ 'Protocol' ] = $this->Buffer->GetByte( );
$Server[ 'Dedicated' ] = Chr( $this->Buffer->GetByte( ) );
$Server[ 'Os' ] = Chr( $this->Buffer->GetByte( ) );
$Server[ 'Password' ] = $this->Buffer->GetByte( ) === 1;
$Server[ 'IsMod' ] = $this->Buffer->GetByte( ) === 1;
if( $Server[ 'IsMod' ] )
{
$Mod[ 'Url' ] = $this->Buffer->GetString( );
$Mod[ 'Download' ] = $this->Buffer->GetString( );
$this->Buffer->Get( 1 ); // NULL byte
$Mod[ 'Version' ] = $this->Buffer->GetLong( );
$Mod[ 'Size' ] = $this->Buffer->GetLong( );
$Mod[ 'ServerSide' ] = $this->Buffer->GetByte( ) === 1;
$Mod[ 'CustomDLL' ] = $this->Buffer->GetByte( ) === 1;
}
$Server[ 'Secure' ] = $this->Buffer->GetByte( ) === 1;
$Server[ 'Bots' ] = $this->Buffer->GetByte( );
if( isset( $Mod ) )
{
$Server[ 'Mod' ] = $Mod;
}
return $Server;
}
if( $Type !== self :: S2A_INFO )
{
throw new InvalidPacketException( 'GetInfo: Packet header mismatch. (0x' . DecHex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
$Server[ 'Protocol' ] = $this->Buffer->GetByte( );
$Server[ 'HostName' ] = $this->Buffer->GetString( );
$Server[ 'Map' ] = $this->Buffer->GetString( );
$Server[ 'ModDir' ] = $this->Buffer->GetString( );
$Server[ 'ModDesc' ] = $this->Buffer->GetString( );
$Server[ 'AppID' ] = $this->Buffer->GetShort( );
$Server[ 'Players' ] = $this->Buffer->GetByte( );
$Server[ 'MaxPlayers' ] = $this->Buffer->GetByte( );
$Server[ 'Bots' ] = $this->Buffer->GetByte( );
$Server[ 'Dedicated' ] = Chr( $this->Buffer->GetByte( ) );
$Server[ 'Os' ] = Chr( $this->Buffer->GetByte( ) );
$Server[ 'Password' ] = $this->Buffer->GetByte( ) === 1;
$Server[ 'Secure' ] = $this->Buffer->GetByte( ) === 1;
// The Ship (they violate query protocol spec by modifying the response)
if( $Server[ 'AppID' ] === 2400 )
{
$Server[ 'GameMode' ] = $this->Buffer->GetByte( );
$Server[ 'WitnessCount' ] = $this->Buffer->GetByte( );
$Server[ 'WitnessTime' ] = $this->Buffer->GetByte( );
}
$Server[ 'Version' ] = $this->Buffer->GetString( );
// Extra Data Flags
if( $this->Buffer->Remaining( ) > 0 )
{
$Server[ 'ExtraDataFlags' ] = $Flags = $this->Buffer->GetByte( );
// The server's game port
if( $Flags & 0x80 )
{
$Server[ 'GamePort' ] = $this->Buffer->GetShort( );
}
// The server's SteamID - does this serve any purpose?
if( $Flags & 0x10 )
{
$Server[ 'ServerID' ] = $this->Buffer->GetUnsignedLong( ) | ( $this->Buffer->GetUnsignedLong( ) << 32 ); // TODO: verify this
}
// The spectator port and then the spectator server name
if( $Flags & 0x40 )
{
$Server[ 'SpecPort' ] = $this->Buffer->GetShort( );
$Server[ 'SpecName' ] = $this->Buffer->GetString( );
}
// The game tag data string for the server
if( $Flags & 0x20 )
{
$Server[ 'GameTags' ] = $this->Buffer->GetString( );
}
// GameID -- alternative to AppID?
if( $Flags & 0x01 )
{
$Server[ 'GameID' ] = $this->Buffer->GetUnsignedLong( ) | ( $this->Buffer->GetUnsignedLong( ) << 32 );
}
if( $this->Buffer->Remaining( ) > 0 )
{
throw new InvalidPacketException( 'GetInfo: unread data? ' . $this->Buffer->Remaining( ) . ' bytes remaining in the buffer. Please report it to the library developer.',
InvalidPacketException::BUFFER_NOT_EMPTY );
}
}
return $Server;
}
/**
* Get players on the server
*
* @throws InvalidPacketException
*
* @return bool|array Returns array with players on success, false on failure
*/
public function GetPlayers( )
{
if( !$this->Connected )
{
return false;
}
switch( $this->GetChallenge( self :: A2S_PLAYER, self :: S2A_PLAYER ) )
{
case self :: GETCHALLENGE_FAILED:
{
return false;
}
case self :: GETCHALLENGE_ALL_CLEAR:
{
$this->Socket->Write( self :: A2S_PLAYER, $this->Challenge );
$this->Socket->Read( 14000 ); // Moronic Arma 3 developers do not split their packets, so we have to read more data
// This violates the protocol spec, and they probably should fix it: https://developer.valvesoftware.com/wiki/Server_queries#Protocol
$Type = $this->Buffer->GetByte( );
if( $Type === 0 )
{
return false;
}
else if( $Type !== self :: S2A_PLAYER )
{
throw new InvalidPacketException( 'GetPlayers: Packet header mismatch. (0x' . DecHex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
break;
}
}
$Players = Array( );
$Count = $this->Buffer->GetByte( );
while( $Count-- > 0 && $this->Buffer->Remaining( ) > 0 )
{
$Player[ 'Id' ] = $this->Buffer->GetByte( ); // PlayerID, is it just always 0?
$Player[ 'Name' ] = $this->Buffer->GetString( );
$Player[ 'Frags' ] = $this->Buffer->GetLong( );
$Player[ 'Time' ] = (int)$this->Buffer->GetFloat( );
$Player[ 'TimeF' ] = GMDate( ( $Player[ 'Time' ] > 3600 ? "H:i:s" : "i:s" ), $Player[ 'Time' ] );
$Players[ ] = $Player;
}
return $Players;
}
/**
* Get rules (cvars) from the server
*
* @throws InvalidPacketException
*
* @return bool|array Returns array with rules on success, false on failure
*/
public function GetRules( )
{
if( !$this->Connected )
{
return false;
}
switch( $this->GetChallenge( self :: A2S_RULES, self :: S2A_RULES ) )
{
case self :: GETCHALLENGE_FAILED:
{
return false;
}
case self :: GETCHALLENGE_ALL_CLEAR:
{
$this->Socket->Write( self :: A2S_RULES, $this->Challenge );
$this->Socket->Read( );
$Type = $this->Buffer->GetByte( );
if( $Type === 0 )
{
return false;
}
else if( $Type !== self :: S2A_RULES )
{
throw new InvalidPacketException( 'GetRules: Packet header mismatch. (0x' . DecHex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
break;
}
}
$Rules = Array( );
$Count = $this->Buffer->GetShort( );
while( $Count-- > 0 && $this->Buffer->Remaining( ) > 0 )
{
$Rule = $this->Buffer->GetString( );
$Value = $this->Buffer->GetString( );
if( !Empty( $Rule ) )
{
$Rules[ $Rule ] = $Value;
}
}
return $Rules;
}
/**
* Get challenge (used for players/rules packets)
*
* @param $Header
* @param $ExpectedResult
* @throws InvalidPacketException
* @return bool True if all went well, false if server uses old GoldSource protocol, and it already contains answer
*/
private function GetChallenge( $Header, $ExpectedResult )
{
if( $this->Challenge )
{
return self :: GETCHALLENGE_ALL_CLEAR;
}
if( $this->UseOldGetChallengeMethod )
{
$Header = self :: A2S_SERVERQUERY_GETCHALLENGE;
}
$this->Socket->Write( $Header, 0xFFFFFFFF );
$this->Socket->Read( );
$Type = $this->Buffer->GetByte( );
switch( $Type )
{
case self :: S2A_CHALLENGE:
{
$this->Challenge = $this->Buffer->Get( 4 );
return self :: GETCHALLENGE_ALL_CLEAR;
}
case $ExpectedResult:
{
// Goldsource (HLTV)
return self :: GETCHALLENGE_CONTAINS_ANSWER;
}
case 0:
{
return self :: GETCHALLENGE_FAILED;
}
default:
{
throw new InvalidPacketException( 'GetChallenge: Packet header mismatch. (0x' . DecHex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
}
}
/**
* Sets rcon password, for future use in Rcon()
*
* @param string $Password Rcon Password
*
* @return bool True on success, false on failure
*/
public function SetRconPassword( $Password )
{
if( !$this->Connected )
{
return false;
}
switch( $this->Socket->Engine )
{
case SourceQuery :: GOLDSOURCE:
{
$this->Rcon = new SourceQueryGoldSourceRcon( $this->Buffer, $this->Socket );
break;
}
case SourceQuery :: SOURCE:
{
$this->Rcon = new SourceQuerySourceRcon( $this->Buffer, $this->Socket );
break;
}
}
$this->Rcon->Open( );
return $this->Rcon->Authorize( $Password );
}
/**
* Sends a command to the server for execution.
*
* @param string $Command Command to execute
*
* @return string|bool Answer from server in string, false on failure
*/
public function Rcon( $Command )
{
if( !$this->Connected )
{
return false;
}
return $this->Rcon->Command( $Command );
}
}
+569
View File
@@ -0,0 +1,569 @@
<?php
/**
* This class provides the public interface to the PHP-Source-Query library.
*
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*/
namespace xPaw\SourceQuery;
use xPaw\SourceQuery\Exception\AuthenticationException;
use xPaw\SourceQuery\Exception\InvalidArgumentException;
use xPaw\SourceQuery\Exception\InvalidPacketException;
use xPaw\SourceQuery\Exception\SocketException;
/**
* Class SourceQuery
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\AuthenticationException
* @uses xPaw\SourceQuery\Exception\InvalidArgumentException
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
* @uses xPaw\SourceQuery\Exception\SocketException
*/
class SourceQuery
{
/**
* Engines
*/
const GOLDSOURCE = 0;
const SOURCE = 1;
/**
* Packets sent
*/
const A2S_PING = 0x69;
const A2S_INFO = 0x54;
const A2S_PLAYER = 0x55;
const A2S_RULES = 0x56;
const A2S_SERVERQUERY_GETCHALLENGE = 0x57;
/**
* Packets received
*/
const S2A_PING = 0x6A;
const S2A_CHALLENGE = 0x41;
const S2A_INFO = 0x49;
const S2A_INFO_OLD = 0x6D; // Old GoldSource, HLTV uses it
const S2A_PLAYER = 0x44;
const S2A_RULES = 0x45;
const S2A_RCON = 0x6C;
/**
* Source rcon sent
*/
const SERVERDATA_EXECCOMMAND = 2;
const SERVERDATA_AUTH = 3;
/**
* Source rcon received
*/
const SERVERDATA_RESPONSE_VALUE = 0;
const SERVERDATA_AUTH_RESPONSE = 2;
/**
* Points to rcon class
*
* @var SourceRcon|GoldSourceRcon|null
*/
private $Rcon;
/**
* Points to socket class
*/
private BaseSocket $Socket;
/**
* True if connection is open, false if not
*/
private bool $Connected = false;
/**
* Contains challenge
*/
private string $Challenge = '';
/**
* Use old method for getting challenge number
*/
private bool $UseOldGetChallengeMethod = false;
public function __construct( BaseSocket $Socket = null )
{
$this->Socket = $Socket ?: new Socket( );
}
public function __destruct( )
{
$this->Disconnect( );
}
/**
* Opens connection to server
*
* @param string $Address Server ip
* @param int $Port Server port
* @param int $Timeout Timeout period
* @param int $Engine Engine the server runs on (goldsource, source)
*
* @throws InvalidArgumentException
* @throws SocketException
*/
public function Connect( string $Address, int $Port, int $Timeout = 3, int $Engine = self::SOURCE ) : void
{
$this->Disconnect( );
if( $Timeout < 0 )
{
throw new InvalidArgumentException( 'Timeout must be a positive integer.', InvalidArgumentException::TIMEOUT_NOT_INTEGER );
}
$this->Socket->Open( $Address, $Port, $Timeout, $Engine );
$this->Connected = true;
}
/**
* Forces GetChallenge to use old method for challenge retrieval because some games use outdated protocol (e.g Starbound)
*
* @param bool $Value Set to true to force old method
*
* @returns bool Previous value
*/
public function SetUseOldGetChallengeMethod( bool $Value ) : bool
{
$Previous = $this->UseOldGetChallengeMethod;
$this->UseOldGetChallengeMethod = $Value === true;
return $Previous;
}
/**
* Closes all open connections
*/
public function Disconnect( ) : void
{
$this->Connected = false;
$this->Challenge = '';
$this->Socket->Close( );
if( $this->Rcon )
{
$this->Rcon->Close( );
$this->Rcon = null;
}
}
/**
* Sends ping packet to the server
* NOTE: This may not work on some games (TF2 for example)
*
* @throws InvalidPacketException
* @throws SocketException
*
* @return bool True on success, false on failure
*/
public function Ping( ) : bool
{
if( !$this->Connected )
{
throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
}
$this->Socket->Write( self::A2S_PING );
$Buffer = $this->Socket->Read( );
return $Buffer->GetByte( ) === self::S2A_PING;
}
/**
* Get server information
*
* @throws InvalidPacketException
* @throws SocketException
*
* @return array Returns an array with information on success
*/
public function GetInfo( ) : array
{
if( !$this->Connected )
{
throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
}
if( $this->Challenge )
{
$this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" . $this->Challenge );
}
else
{
$this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" );
}
$Buffer = $this->Socket->Read( );
$Type = $Buffer->GetByte( );
$Server = [];
if( $Type === self::S2A_CHALLENGE )
{
$this->Challenge = $Buffer->Get( 4 );
$this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" . $this->Challenge );
$Buffer = $this->Socket->Read( );
$Type = $Buffer->GetByte( );
}
// Old GoldSource protocol, HLTV still uses it
if( $Type === self::S2A_INFO_OLD && $this->Socket->Engine === self::GOLDSOURCE )
{
/**
* If we try to read data again, and we get the result with type S2A_INFO (0x49)
* That means this server is running dproto,
* Because it sends answer for both protocols
*/
$Server[ 'Address' ] = $Buffer->GetString( );
$Server[ 'HostName' ] = $Buffer->GetString( );
$Server[ 'Map' ] = $Buffer->GetString( );
$Server[ 'ModDir' ] = $Buffer->GetString( );
$Server[ 'ModDesc' ] = $Buffer->GetString( );
$Server[ 'Players' ] = $Buffer->GetByte( );
$Server[ 'MaxPlayers' ] = $Buffer->GetByte( );
$Server[ 'Protocol' ] = $Buffer->GetByte( );
$Server[ 'Dedicated' ] = Chr( $Buffer->GetByte( ) );
$Server[ 'Os' ] = Chr( $Buffer->GetByte( ) );
$Server[ 'Password' ] = $Buffer->GetByte( ) === 1;
$Server[ 'IsMod' ] = $Buffer->GetByte( ) === 1;
if( $Server[ 'IsMod' ] )
{
$Mod = [];
$Mod[ 'Url' ] = $Buffer->GetString( );
$Mod[ 'Download' ] = $Buffer->GetString( );
$Buffer->Get( 1 ); // NULL byte
$Mod[ 'Version' ] = $Buffer->GetLong( );
$Mod[ 'Size' ] = $Buffer->GetLong( );
$Mod[ 'ServerSide' ] = $Buffer->GetByte( ) === 1;
$Mod[ 'CustomDLL' ] = $Buffer->GetByte( ) === 1;
$Server[ 'Mod' ] = $Mod;
}
$Server[ 'Secure' ] = $Buffer->GetByte( ) === 1;
$Server[ 'Bots' ] = $Buffer->GetByte( );
return $Server;
}
if( $Type !== self::S2A_INFO )
{
throw new InvalidPacketException( 'GetInfo: Packet header mismatch. (0x' . DecHex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
$Server[ 'Protocol' ] = $Buffer->GetByte( );
$Server[ 'HostName' ] = $Buffer->GetString( );
$Server[ 'Map' ] = $Buffer->GetString( );
$Server[ 'ModDir' ] = $Buffer->GetString( );
$Server[ 'ModDesc' ] = $Buffer->GetString( );
$Server[ 'AppID' ] = $Buffer->GetShort( );
$Server[ 'Players' ] = $Buffer->GetByte( );
$Server[ 'MaxPlayers' ] = $Buffer->GetByte( );
$Server[ 'Bots' ] = $Buffer->GetByte( );
$Server[ 'Dedicated' ] = Chr( $Buffer->GetByte( ) );
$Server[ 'Os' ] = Chr( $Buffer->GetByte( ) );
$Server[ 'Password' ] = $Buffer->GetByte( ) === 1;
$Server[ 'Secure' ] = $Buffer->GetByte( ) === 1;
// The Ship (they violate query protocol spec by modifying the response)
if( $Server[ 'AppID' ] === 2400 )
{
$Server[ 'GameMode' ] = $Buffer->GetByte( );
$Server[ 'WitnessCount' ] = $Buffer->GetByte( );
$Server[ 'WitnessTime' ] = $Buffer->GetByte( );
}
$Server[ 'Version' ] = $Buffer->GetString( );
// Extra Data Flags
if( $Buffer->Remaining( ) > 0 )
{
$Server[ 'ExtraDataFlags' ] = $Flags = $Buffer->GetByte( );
// S2A_EXTRA_DATA_HAS_GAME_PORT - Next 2 bytes include the game port.
if( $Flags & 0x80 )
{
$Server[ 'GamePort' ] = $Buffer->GetShort( );
}
// S2A_EXTRA_DATA_HAS_STEAMID - Next 8 bytes are the steamID
// Want to play around with this?
// You can use https://github.com/xPaw/SteamID.php
if( $Flags & 0x10 )
{
$SteamIDLower = $Buffer->GetUnsignedLong( );
$SteamIDInstance = $Buffer->GetUnsignedLong( ); // This gets shifted by 32 bits, which should be steamid instance
$SteamID = 0;
if( PHP_INT_SIZE === 4 )
{
if( extension_loaded( 'gmp' ) )
{
$SteamIDLower = gmp_abs( $SteamIDLower );
$SteamIDInstance = gmp_abs( $SteamIDInstance );
$SteamID = gmp_strval( gmp_or( $SteamIDLower, gmp_mul( $SteamIDInstance, gmp_pow( 2, 32 ) ) ) );
}
else
{
throw new \RuntimeException( 'Either 64-bit PHP installation or "gmp" module is required to correctly parse server\'s steamid.' );
}
}
else
{
$SteamID = $SteamIDLower | ( $SteamIDInstance << 32 );
}
$Server[ 'SteamID' ] = $SteamID;
unset( $SteamIDLower, $SteamIDInstance, $SteamID );
}
// S2A_EXTRA_DATA_HAS_SPECTATOR_DATA - Next 2 bytes include the spectator port, then the spectator server name.
if( $Flags & 0x40 )
{
$Server[ 'SpecPort' ] = $Buffer->GetShort( );
$Server[ 'SpecName' ] = $Buffer->GetString( );
}
// S2A_EXTRA_DATA_HAS_GAMETAG_DATA - Next bytes are the game tag string
if( $Flags & 0x20 )
{
$Server[ 'GameTags' ] = $Buffer->GetString( );
}
// S2A_EXTRA_DATA_GAMEID - Next 8 bytes are the gameID of the server
if( $Flags & 0x01 )
{
$Server[ 'GameID' ] = $Buffer->GetUnsignedLong( ) | ( $Buffer->GetUnsignedLong( ) << 32 );
}
if( $Buffer->Remaining( ) > 0 )
{
throw new InvalidPacketException( 'GetInfo: unread data? ' . $Buffer->Remaining( ) . ' bytes remaining in the buffer. Please report it to the library developer.',
InvalidPacketException::BUFFER_NOT_EMPTY );
}
}
return $Server;
}
/**
* Get players on the server
*
* @throws InvalidPacketException
* @throws SocketException
*
* @return array Returns an array with players on success
*/
public function GetPlayers( ) : array
{
if( !$this->Connected )
{
throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
}
$this->GetChallenge( self::A2S_PLAYER, self::S2A_PLAYER );
$this->Socket->Write( self::A2S_PLAYER, $this->Challenge );
$Buffer = $this->Socket->Read( 14000 ); // Moronic Arma 3 developers do not split their packets, so we have to read more data
// This violates the protocol spec, and they probably should fix it: https://developer.valvesoftware.com/wiki/Server_queries#Protocol
$Type = $Buffer->GetByte( );
if( $Type !== self::S2A_PLAYER )
{
throw new InvalidPacketException( 'GetPlayers: Packet header mismatch. (0x' . DecHex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
$Players = [];
$Count = $Buffer->GetByte( );
while( $Count-- > 0 && $Buffer->Remaining( ) > 0 )
{
$Player = [];
$Player[ 'Id' ] = $Buffer->GetByte( ); // PlayerID, is it just always 0?
$Player[ 'Name' ] = $Buffer->GetString( );
$Player[ 'Frags' ] = $Buffer->GetLong( );
$Player[ 'Time' ] = (int)$Buffer->GetFloat( );
$Player[ 'TimeF' ] = GMDate( ( $Player[ 'Time' ] > 3600 ? "H:i:s" : "i:s" ), $Player[ 'Time' ] );
$Players[ ] = $Player;
}
return $Players;
}
/**
* Get rules (cvars) from the server
*
* @throws InvalidPacketException
* @throws SocketException
*
* @return array Returns an array with rules on success
*/
public function GetRules( ) : array
{
if( !$this->Connected )
{
throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
}
$this->GetChallenge( self::A2S_RULES, self::S2A_RULES );
$this->Socket->Write( self::A2S_RULES, $this->Challenge );
$Buffer = $this->Socket->Read( );
$Type = $Buffer->GetByte( );
if( $Type !== self::S2A_RULES )
{
throw new InvalidPacketException( 'GetRules: Packet header mismatch. (0x' . DecHex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
$Rules = [];
$Count = $Buffer->GetShort( );
while( $Count-- > 0 && $Buffer->Remaining( ) > 0 )
{
$Rule = $Buffer->GetString( );
$Value = $Buffer->GetString( );
if( !Empty( $Rule ) )
{
$Rules[ $Rule ] = $Value;
}
}
return $Rules;
}
/**
* Get challenge (used for players/rules packets)
*
* @throws InvalidPacketException
*/
private function GetChallenge( int $Header, int $ExpectedResult ) : void
{
if( $this->Challenge )
{
return;
}
if( $this->UseOldGetChallengeMethod )
{
$Header = self::A2S_SERVERQUERY_GETCHALLENGE;
}
$this->Socket->Write( $Header, "\xFF\xFF\xFF\xFF" );
$Buffer = $this->Socket->Read( );
$Type = $Buffer->GetByte( );
switch( $Type )
{
case self::S2A_CHALLENGE:
{
$this->Challenge = $Buffer->Get( 4 );
return;
}
case $ExpectedResult:
{
// Goldsource (HLTV)
return;
}
case 0:
{
throw new InvalidPacketException( 'GetChallenge: Failed to get challenge.' );
}
default:
{
throw new InvalidPacketException( 'GetChallenge: Packet header mismatch. (0x' . DecHex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
}
}
/**
* Sets rcon password, for future use in Rcon()
*
* @param string $Password Rcon Password
*
* @throws AuthenticationException
* @throws InvalidPacketException
* @throws SocketException
*/
public function SetRconPassword( string $Password ) : void
{
if( !$this->Connected )
{
throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
}
switch( $this->Socket->Engine )
{
case SourceQuery::GOLDSOURCE:
{
$this->Rcon = new GoldSourceRcon( $this->Socket );
break;
}
case SourceQuery::SOURCE:
{
$this->Rcon = new SourceRcon( $this->Socket );
break;
}
default:
{
throw new SocketException( 'Unknown engine.', SocketException::INVALID_ENGINE );
}
}
$this->Rcon->Open( );
$this->Rcon->Authorize( $Password );
}
/**
* Sends a command to the server for execution.
*
* @param string $Command Command to execute
*
* @throws AuthenticationException
* @throws InvalidPacketException
* @throws SocketException
*
* @return string Answer from server in string
*/
public function Rcon( string $Command ) : string
{
if( !$this->Connected )
{
throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
}
if( $this->Rcon === null )
{
throw new SocketException( 'You must set a RCON password before trying to execute a RCON command.', SocketException::NOT_CONNECTED );
}
return $this->Rcon->Command( $Command );
}
}
-194
View File
@@ -1,194 +0,0 @@
<?php
/**
* Class written by xPaw
*
* Website: https://xpaw.me
* GitHub: https://github.com/xPaw/PHP-Source-Query-Class
*/
use xPaw\SourceQuery\Exception\AuthenticationException;
use xPaw\SourceQuery\Exception\TimeoutException;
use xPaw\SourceQuery\Exception\InvalidPacketException;
class SourceQuerySourceRcon
{
/**
* Points to buffer class
*
* @var SourceQueryBuffer
*/
private $Buffer;
/**
* Points to socket class
*
* @var SourceQuerySocket
*/
private $Socket;
private $RconSocket;
private $RconRequestId;
public function __construct( $Buffer, $Socket )
{
$this->Buffer = $Buffer;
$this->Socket = $Socket;
}
public function Close( )
{
if( $this->RconSocket )
{
FClose( $this->RconSocket );
$this->RconSocket = null;
}
$this->RconRequestId = 0;
}
public function Open( )
{
if( !$this->RconSocket )
{
$this->RconSocket = @FSockOpen( $this->Socket->Ip, $this->Socket->Port, $ErrNo, $ErrStr, $this->Socket->Timeout );
if( $ErrNo || !$this->RconSocket )
{
throw new TimeoutException( 'Can\'t connect to RCON server: ' . $ErrStr, TimeoutException::TIMEOUT_CONNECT );
}
Stream_Set_Timeout( $this->RconSocket, $this->Socket->Timeout );
Stream_Set_Blocking( $this->RconSocket, true );
}
}
public function Write( $Header, $String = '' )
{
// Pack the packet together
$Command = Pack( 'VV', ++$this->RconRequestId, $Header ) . $String . "\x00\x00";
// Prepend packet length
$Command = Pack( 'V', StrLen( $Command ) ) . $Command;
$Length = StrLen( $Command );
return $Length === FWrite( $this->RconSocket, $Command, $Length );
}
public function Read( )
{
$this->Buffer->Set( FRead( $this->RconSocket, 4 ) );
if( $this->Buffer->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Rcon read: Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
}
$PacketSize = $this->Buffer->GetLong( );
$this->Buffer->Set( FRead( $this->RconSocket, $PacketSize ) );
$Buffer = $this->Buffer->Get( );
$Remaining = $PacketSize - StrLen( $Buffer );
while( $Remaining > 0 )
{
$Buffer2 = FRead( $this->RconSocket, $Remaining );
$PacketSize = StrLen( $Buffer2 );
if( $PacketSize === 0 )
{
throw new InvalidPacketException( 'Read ' . strlen( $Buffer ) . ' bytes from socket, ' . $Remaining . ' remaining', InvalidPacketException::BUFFER_EMPTY );
break;
}
$Buffer .= $Buffer2;
$Remaining -= $PacketSize;
}
$this->Buffer->Set( $Buffer );
}
public function Command( $Command )
{
$this->Write( SourceQuery :: SERVERDATA_EXECCOMMAND, $Command );
$this->Read( );
$this->Buffer->GetLong( ); // RequestID
$Type = $this->Buffer->GetLong( );
if( $Type === SourceQuery :: SERVERDATA_AUTH_RESPONSE )
{
throw new AuthenticationException( 'Bad rcon_password.', AuthenticationException::BAD_PASSWORD );
}
else if( $Type !== SourceQuery :: SERVERDATA_RESPONSE_VALUE )
{
return false;
}
$Buffer = $this->Buffer->Get( );
// We do this stupid hack to handle split packets
// See https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses
if( StrLen( $Buffer ) >= 4000 )
{
do
{
$this->Write( SourceQuery :: SERVERDATA_RESPONSE_VALUE );
$this->Read( );
$this->Buffer->GetLong( ); // RequestID
if( $this->Buffer->GetLong( ) !== SourceQuery :: SERVERDATA_RESPONSE_VALUE )
{
break;
}
$Buffer2 = $this->Buffer->Get( );
if( $Buffer2 === "\x00\x01\x00\x00\x00\x00" )
{
break;
}
$Buffer .= $Buffer2;
}
while( true );
}
return rtrim( $Buffer, "\0" );
}
public function Authorize( $Password )
{
$this->Write( SourceQuery :: SERVERDATA_AUTH, $Password );
$this->Read( );
$RequestID = $this->Buffer->GetLong( );
$Type = $this->Buffer->GetLong( );
// If we receive SERVERDATA_RESPONSE_VALUE, then we need to read again
// More info: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Additional_Comments
if( $Type === SourceQuery :: SERVERDATA_RESPONSE_VALUE )
{
$this->Read( );
$RequestID = $this->Buffer->GetLong( );
$Type = $this->Buffer->GetLong( );
}
if( $RequestID === -1 || $Type !== SourceQuery :: SERVERDATA_AUTH_RESPONSE )
{
throw new AuthenticationException( 'RCON authorization failed.', AuthenticationException::BAD_PASSWORD );
}
return true;
}
}
+200
View File
@@ -0,0 +1,200 @@
<?php
/**
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/
namespace xPaw\SourceQuery;
use xPaw\SourceQuery\Exception\AuthenticationException;
use xPaw\SourceQuery\Exception\InvalidPacketException;
use xPaw\SourceQuery\Exception\SocketException;
/**
* Class SourceRcon
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\AuthenticationException
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
* @uses xPaw\SourceQuery\Exception\SocketException
*/
class SourceRcon
{
/**
* Points to socket class
*/
private BaseSocket $Socket;
/** @var resource */
private $RconSocket;
private int $RconRequestId = 0;
public function __construct( BaseSocket $Socket )
{
$this->Socket = $Socket;
}
public function Close( ) : void
{
if( $this->RconSocket )
{
FClose( $this->RconSocket );
$this->RconSocket = null;
}
$this->RconRequestId = 0;
}
public function Open( ) : void
{
if( !$this->RconSocket )
{
$this->RconSocket = @FSockOpen( $this->Socket->Address, $this->Socket->Port, $ErrNo, $ErrStr, $this->Socket->Timeout );
if( $ErrNo || !$this->RconSocket )
{
throw new SocketException( 'Can\'t connect to RCON server: ' . $ErrStr, SocketException::CONNECTION_FAILED );
}
Stream_Set_Timeout( $this->RconSocket, $this->Socket->Timeout );
Stream_Set_Blocking( $this->RconSocket, true );
}
}
public function Write( int $Header, string $String = '' ) : bool
{
// Pack the packet together
$Command = Pack( 'VV', ++$this->RconRequestId, $Header ) . $String . "\x00\x00";
// Prepend packet length
$Command = Pack( 'V', StrLen( $Command ) ) . $Command;
$Length = StrLen( $Command );
return $Length === FWrite( $this->RconSocket, $Command, $Length );
}
public function Read( ) : Buffer
{
$Buffer = new Buffer( );
$Buffer->Set( FRead( $this->RconSocket, 4 ) );
if( $Buffer->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Rcon read: Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
}
$PacketSize = $Buffer->GetLong( );
$Buffer->Set( FRead( $this->RconSocket, $PacketSize ) );
$Data = $Buffer->Get( );
$Remaining = $PacketSize - StrLen( $Data );
while( $Remaining > 0 )
{
$Data2 = FRead( $this->RconSocket, $Remaining );
$PacketSize = StrLen( $Data2 );
if( $PacketSize === 0 )
{
throw new InvalidPacketException( 'Read ' . strlen( $Data ) . ' bytes from socket, ' . $Remaining . ' remaining', InvalidPacketException::BUFFER_EMPTY );
break;
}
$Data .= $Data2;
$Remaining -= $PacketSize;
}
$Buffer->Set( $Data );
return $Buffer;
}
public function Command( string $Command ) : string
{
$this->Write( SourceQuery::SERVERDATA_EXECCOMMAND, $Command );
$Buffer = $this->Read( );
$Buffer->GetLong( ); // RequestID
$Type = $Buffer->GetLong( );
if( $Type === SourceQuery::SERVERDATA_AUTH_RESPONSE )
{
throw new AuthenticationException( 'Bad rcon_password.', AuthenticationException::BAD_PASSWORD );
}
else if( $Type !== SourceQuery::SERVERDATA_RESPONSE_VALUE )
{
throw new InvalidPacketException( 'Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH );
}
$Data = $Buffer->Get( );
// We do this stupid hack to handle split packets
// See https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses
if( StrLen( $Data ) >= 4000 )
{
$this->Write( SourceQuery::SERVERDATA_RESPONSE_VALUE );
do
{
$Buffer = $this->Read( );
$Buffer->GetLong( ); // RequestID
if( $Buffer->GetLong( ) !== SourceQuery::SERVERDATA_RESPONSE_VALUE )
{
break;
}
$Data2 = $Buffer->Get( );
if( $Data2 === "\x00\x01\x00\x00\x00\x00" )
{
break;
}
$Data .= $Data2;
}
while( true );
}
return rtrim( $Data, "\0" );
}
public function Authorize( string $Password ) : void
{
$this->Write( SourceQuery::SERVERDATA_AUTH, $Password );
$Buffer = $this->Read( );
$RequestID = $Buffer->GetLong( );
$Type = $Buffer->GetLong( );
// If we receive SERVERDATA_RESPONSE_VALUE, then we need to read again
// More info: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Additional_Comments
if( $Type === SourceQuery::SERVERDATA_RESPONSE_VALUE )
{
$Buffer = $this->Read( );
$RequestID = $Buffer->GetLong( );
$Type = $Buffer->GetLong( );
}
if( $RequestID === -1 || $Type !== SourceQuery::SERVERDATA_AUTH_RESPONSE )
{
throw new AuthenticationException( 'RCON authorization failed.', AuthenticationException::BAD_PASSWORD );
}
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
/**
* Library to query servers that implement Source Engine Query protocol.
*
* Special thanks to koraktor for his awesome Steam Condenser class,
* I used it as a reference at some points.
*
* @author Pavel Djundik
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*/
require_once __DIR__ . '/Exception/SourceQueryException.php';
require_once __DIR__ . '/Exception/AuthenticationException.php';
require_once __DIR__ . '/Exception/InvalidArgumentException.php';
require_once __DIR__ . '/Exception/SocketException.php';
require_once __DIR__ . '/Exception/InvalidPacketException.php';
require_once __DIR__ . '/Buffer.php';
require_once __DIR__ . '/BaseSocket.php';
require_once __DIR__ . '/Socket.php';
require_once __DIR__ . '/SourceRcon.php';
require_once __DIR__ . '/GoldSourceRcon.php';
require_once __DIR__ . '/SourceQuery.php';
+21
View File
@@ -0,0 +1,21 @@
{
"Protocol": 17,
"HostName": "BombGame by xPaw & Co.",
"Map": "de_dust2",
"ModDir": "csgo",
"ModDesc": "Counter-Strike: Global Offensive",
"AppID": 730,
"Players": 0,
"MaxPlayers": 16,
"Bots": 0,
"Dedicated": "d",
"Os": "l",
"Password": false,
"Secure": true,
"Version": "1.35.0.7",
"ExtraDataFlags": 177,
"GamePort": 27036,
"SteamID": 90097713628897284,
"GameTags": "empty,*grp:1105381i,bombgame,secure",
"GameID": 730
}
+1
View File
@@ -0,0 +1 @@
ffffffff4911426f6d6247616d652062792078506177202620436f2e0064655f6475737432006373676f00436f756e7465722d537472696b653a20476c6f62616c204f6666656e7369766500da02001000646c0001312e33352e302e3700b19c6904e0eca764174001656d7074792c2a6772703a31313035333831692c626f6d6267616d652c73656375726500da02000000000000
+21
View File
@@ -0,0 +1,21 @@
{
"Protocol": 17,
"HostName": "Город Инноваций | Русский DarkRP",
"Map": "rp_bangclaw",
"ModDir": "garrysmod",
"ModDesc": "DarkRP",
"AppID": 4000,
"Players": 33,
"MaxPlayers": 40,
"Bots": 0,
"Dedicated": "d",
"Os": "l",
"Password": false,
"Secure": true,
"Version": "15.08.10",
"ExtraDataFlags": 177,
"GamePort": 27015,
"SteamID": 90097724371517447,
"GameTags": " gm:darkrp",
"GameID": 4000
}
+1
View File
@@ -0,0 +1 @@
ffffffff4911d093d0bed180d0bed0b420d098d0bdd0bdd0bed0b2d0b0d186d0b8d0b9207c20d0a0d183d181d181d0bad0b8d0b9204461726b52500072705f62616e67636c6177006761727279736d6f64004461726b525000a00f212800646c000131352e30382e313000b1876907403c286717400120676d3a6461726b727000a00f000000000000
+16
View File
@@ -0,0 +1,16 @@
{
"Address": "192.168.1.197:27020",
"HostName": "Castle Mortimus:0",
"Map": "op4_kbase",
"ModDir": "gearbox",
"ModDesc": "HLTV",
"Players": 0,
"MaxPlayers": 1,
"Protocol": 48,
"Dedicated": "p",
"Os": "w",
"Password": false,
"IsMod": false,
"Secure": false,
"Bots": 0
}
+1
View File
@@ -0,0 +1 @@
ffffffff6d3139322e3136382e312e3139373a323730323000436173746c65204d6f7274696d75733a30006f70345f6b626173650067656172626f7800484c5456000001307077000000
+24
View File
@@ -0,0 +1,24 @@
{
"Address": "127.0.0.1:27015",
"HostName": "ClanSC #3 - Engage [Logros]",
"Map": "sc_doc",
"ModDir": "svencoop",
"ModDesc": "Sven Co-op 4.8",
"Players": 0,
"MaxPlayers": 16,
"Protocol": 47,
"Dedicated": "d",
"Os": "w",
"Password": true,
"IsMod": true,
"Secure": false,
"Bots": 0,
"Mod": {
"Url": "",
"Download": "",
"Version": 1,
"Size": 0,
"ServerSide": true,
"CustomDLL": false
}
}
+1
View File
@@ -0,0 +1 @@
ffffffff6d3132372e302e302e313a323730313500436c616e5343202333202d20456e67616765205b4c6f67726f735d0073635f646f63007376656e636f6f70005376656e20436f2d6f7020342e380000102f64770101000000010000000000000001000000
+23
View File
@@ -0,0 +1,23 @@
{
"Protocol": 17,
"HostName": " FirePowered.org | Unusual Trade | !jackpot",
"Map": "trade_unusual_center_v3",
"ModDir": "tf",
"ModDesc": "Unusual Trading",
"AppID": 440,
"Players": 32,
"MaxPlayers": 32,
"Bots": 0,
"Dedicated": "d",
"Os": "l",
"Password": false,
"Secure": true,
"Version": "3032525",
"ExtraDataFlags": 241,
"GamePort": 27045,
"SteamID": 85568392920039468,
"SpecPort": 27050,
"SpecName": "ScamCam",
"GameTags": "FirePowered,alltalk,backpack.tf,increased_maxplayers,no_ads,noads,nopinion,norespawntime,trade,trading,unusual",
"GameID": 440
}
+1
View File
@@ -0,0 +1 @@
ffffffff49112046697265506f77657265642e6f7267207c20556e757375616c205472616465207c20216a61636b706f740074726164655f756e757375616c5f63656e7465725f763300746600556e757375616c2054726164696e6700b801202000646c00013330333235323500f1a5692c00000000003001aa695363616d43616d0046697265506f77657265642c616c6c74616c6b2c6261636b7061636b2e74662c696e637265617365645f6d6178706c61796572732c6e6f5f6164732c6e6f6164732c6e6f70696e696f6e2c6e6f7265737061776e74696d652c74726164652c74726164696e672c756e757375616c00b801000000000000
+19
View File
@@ -0,0 +1,19 @@
{
"Protocol": 7,
"HostName": "RKSzone.com | US Chicago | The Ship | Hunt",
"Map": "atalanta",
"ModDir": "ship",
"ModDesc": "The Ship",
"AppID": 2400,
"Players": 27,
"MaxPlayers": 32,
"Bots": 16,
"Dedicated": "d",
"Os": "w",
"Password": false,
"Secure": true,
"GameMode": 0,
"WitnessCount": 2,
"WitnessTime": 5,
"Version": "1.0.0.16"
}
+1
View File
@@ -0,0 +1 @@
ffffffff4907524b537a6f6e652e636f6d207c205553204368696361676f207c205468652053686970207c2048756e74006174616c616e746100736869700054686520536869700060091b201064770001000205312e302e302e313600
+408
View File
@@ -0,0 +1,408 @@
[
{
"Id": 0,
"Name": "Zien",
"Frags": 0,
"Time": 11538,
"TimeF": "03:12:18"
},
{
"Id": 0,
"Name": "Doffy",
"Frags": 0,
"Time": 8919,
"TimeF": "02:28:39"
},
{
"Id": 0,
"Name": "丶↑뮈지 这是什么鬼",
"Frags": 0,
"Time": 8804,
"TimeF": "02:26:44"
},
{
"Id": 0,
"Name": "LiGGleS",
"Frags": 0,
"Time": 7561,
"TimeF": "02:06:01"
},
{
"Id": 0,
"Name": "☣[AGC]Michael J. Caboose☣",
"Frags": 0,
"Time": 5675,
"TimeF": "01:34:35"
},
{
"Id": 0,
"Name": "decision <vancity_dv>",
"Frags": 0,
"Time": 5339,
"TimeF": "01:28:59"
},
{
"Id": 0,
"Name": "Mime ❝Matheo❞",
"Frags": 0,
"Time": 4296,
"TimeF": "01:11:36"
},
{
"Id": 0,
"Name": "Szp@n3r",
"Frags": 0,
"Time": 3880,
"TimeF": "01:04:40"
},
{
"Id": 0,
"Name": "Foxy | D:<",
"Frags": 0,
"Time": 3746,
"TimeF": "01:02:26"
},
{
"Id": 0,
"Name": "Giguli",
"Frags": 0,
"Time": 3599,
"TimeF": "59:59"
},
{
"Id": 0,
"Name": "Erkxalle",
"Frags": 0,
"Time": 2860,
"TimeF": "47:40"
},
{
"Id": 0,
"Name": "[GlobalEdgelite]DaC0p#fuckturks",
"Frags": 0,
"Time": 2666,
"TimeF": "44:26"
},
{
"Id": 0,
"Name": "Aluminiu",
"Frags": 0,
"Time": 2634,
"TimeF": "43:54"
},
{
"Id": 0,
"Name": "BlackLounge-ZombieSOAD TwinBROS",
"Frags": 0,
"Time": 2499,
"TimeF": "41:39"
},
{
"Id": 0,
"Name": "Captain Buster",
"Frags": 0,
"Time": 2420,
"TimeF": "40:20"
},
{
"Id": 0,
"Name": "thirty seven",
"Frags": 0,
"Time": 2395,
"TimeF": "39:55"
},
{
"Id": 0,
"Name": "!Bastardo!",
"Frags": 0,
"Time": 2274,
"TimeF": "37:54"
},
{
"Id": 0,
"Name": "Saibot™",
"Frags": 0,
"Time": 2240,
"TimeF": "37:20"
},
{
"Id": 0,
"Name": "OG W i l l y",
"Frags": 0,
"Time": 2182,
"TimeF": "36:22"
},
{
"Id": 0,
"Name": "TriXterR",
"Frags": 0,
"Time": 2034,
"TimeF": "33:54"
},
{
"Id": 0,
"Name": "ToadsMansion",
"Frags": 0,
"Time": 1974,
"TimeF": "32:54"
},
{
"Id": 0,
"Name": "Moxshi™",
"Frags": 0,
"Time": 1924,
"TimeF": "32:04"
},
{
"Id": 0,
"Name": "bish",
"Frags": 0,
"Time": 1831,
"TimeF": "30:31"
},
{
"Id": 0,
"Name": "Arakismo",
"Frags": 0,
"Time": 1811,
"TimeF": "30:11"
},
{
"Id": 0,
"Name": "Just_Dank",
"Frags": 0,
"Time": 1753,
"TimeF": "29:13"
},
{
"Id": 0,
"Name": "Aftersh0ck",
"Frags": 0,
"Time": 1724,
"TimeF": "28:44"
},
{
"Id": 0,
"Name": "Saibaman",
"Frags": 0,
"Time": 1635,
"TimeF": "27:15"
},
{
"Id": 0,
"Name": "The Ultimate Gamer",
"Frags": 0,
"Time": 1610,
"TimeF": "26:50"
},
{
"Id": 0,
"Name": "Butter Kaya no Peanut",
"Frags": 0,
"Time": 1549,
"TimeF": "25:49"
},
{
"Id": 0,
"Name": "Lugiia",
"Frags": 0,
"Time": 1488,
"TimeF": "24:48"
},
{
"Id": 0,
"Name": "Hoang Bao",
"Frags": 0,
"Time": 1387,
"TimeF": "23:07"
},
{
"Id": 0,
"Name": "Eldo Prezidento",
"Frags": 0,
"Time": 1344,
"TimeF": "22:24"
},
{
"Id": 0,
"Name": "Sky[C.Y.R]",
"Frags": 0,
"Time": 1306,
"TimeF": "21:46"
},
{
"Id": 0,
"Name": "PunckChop",
"Frags": 0,
"Time": 995,
"TimeF": "16:35"
},
{
"Id": 0,
"Name": "Elmoradian",
"Frags": 0,
"Time": 968,
"TimeF": "16:08"
},
{
"Id": 0,
"Name": "friendshipz",
"Frags": 0,
"Time": 967,
"TimeF": "16:07"
},
{
"Id": 0,
"Name": "Guardios",
"Frags": 0,
"Time": 897,
"TimeF": "14:57"
},
{
"Id": 0,
"Name": "Samuel Luz",
"Frags": 0,
"Time": 779,
"TimeF": "12:59"
},
{
"Id": 0,
"Name": "LordMerkx",
"Frags": 0,
"Time": 756,
"TimeF": "12:36"
},
{
"Id": 0,
"Name": "zeroyjk",
"Frags": 0,
"Time": 698,
"TimeF": "11:38"
},
{
"Id": 0,
"Name": "[yellow][@Rainbow@][blue](moon)",
"Frags": 0,
"Time": 633,
"TimeF": "10:33"
},
{
"Id": 0,
"Name": "XiaoJim(Newbie)",
"Frags": 0,
"Time": 588,
"TimeF": "09:48"
},
{
"Id": 0,
"Name": "George Costanza",
"Frags": 0,
"Time": 568,
"TimeF": "09:28"
},
{
"Id": 0,
"Name": "Boring Bob",
"Frags": 0,
"Time": 565,
"TimeF": "09:25"
},
{
"Id": 0,
"Name": "Chris",
"Frags": 0,
"Time": 559,
"TimeF": "09:19"
},
{
"Id": 0,
"Name": "NLVN",
"Frags": 0,
"Time": 554,
"TimeF": "09:14"
},
{
"Id": 0,
"Name": "Dan Rampage",
"Frags": 0,
"Time": 518,
"TimeF": "08:38"
},
{
"Id": 0,
"Name": "Kikerini",
"Frags": 0,
"Time": 477,
"TimeF": "07:57"
},
{
"Id": 0,
"Name": "waxor123",
"Frags": 0,
"Time": 382,
"TimeF": "06:22"
},
{
"Id": 0,
"Name": "Uzu",
"Frags": 0,
"Time": 286,
"TimeF": "04:46"
},
{
"Id": 0,
"Name": "chewin'_ma_BIC #FREE KAKAROT",
"Frags": 0,
"Time": 265,
"TimeF": "04:25"
},
{
"Id": 0,
"Name": "Milk Tea.",
"Frags": 0,
"Time": 218,
"TimeF": "03:38"
},
{
"Id": 0,
"Name": "Inglorious Bastard",
"Frags": 0,
"Time": 208,
"TimeF": "03:28"
},
{
"Id": 0,
"Name": "Bloody Wolf",
"Frags": 0,
"Time": 184,
"TimeF": "03:04"
},
{
"Id": 0,
"Name": "YurMum",
"Frags": 0,
"Time": 161,
"TimeF": "02:41"
},
{
"Id": 0,
"Name": "Modica",
"Frags": 0,
"Time": 113,
"TimeF": "01:53"
},
{
"Id": 0,
"Name": "[P].R.O.T.O.T.Y.P.E",
"Frags": 0,
"Time": 109,
"TimeF": "01:49"
},
{
"Id": 0,
"Name": "69@War",
"Frags": 0,
"Time": 33,
"TimeF": "00:33"
}
]
+2
View File
@@ -0,0 +1,2 @@
feffffffff8400000200a404ffffffff443a005a69656e0000000000d948344600efbca4efbd8fefbd86efbd86efbd9900000000007a5c0b4600e4b8b6e28691ebae88eca78020e8bf99e698afe4bb80e4b988e9acbc00000000003a920946004c6947476c655300000000005348ec4500e298a35b4147435d4d69636861656c204a2e204361626f6f7365e298a30000000000905ab145006465636973696f6e203c76616e636974795f64763e00000000004edfa645004d696d6520e29d9d4d617468656fe29d9e00000000001940864500537a70406e33720000000000898c724500466f7879207c20443a3c0000000000622c6a4500476967756c690000000000ebfb60450045726b78616c6c65000000000048c93245005b476c6f62616c456467656c6974655d4461433070236675636b7475726b73000000000087a5264500416c756d696e69750000000000c6ac244500426c61636b4c6f756e67652d5a6f6d626965534f4144205477696e42524f530000000000c6381c45004361707461696e204275737465720000000000ac4917450074686972747920736576656e00000000006eb315450021426173746172646f210000000000ed260e4500536169626f74e284a20000000000ae000c45004f4720572069206c206c20790000000000836508450054726958746572520000000000855afe4400546f6164734d616e73696f6e000000000003d1f644004d6f78736869e284a200000000000483f0440062697368000000000096efe444004172616b69736d6f00000000009769e244004a7573745f44616e6b00000000009d27db44004166746572736830636b00000000002680d7440053616962616d616e00000000009274cc440054686520556c74696d6174652047616d65720000000000934bc94400427574746572204b617961206e6f205065616e7574000000000075a0c144004c75676969610000000000fd02ba4400486f616e672042616f0000000000677aad4400456c646f205072657a6964656e746f00000000006612a84400536b795b432e592e525d0000000000e758a3440050756e636b43686f7000000000007bee784400456c6d6f72616469616e00000000008e0b724400667269656e64736869707a000000000090d37144004775617264696f7300000000008e4a60440053616d75656c204c757a0000000000cbdb4244004c6f72644d65726b780000000000c9123d44007a65726f796a6b000000000083a92e44005b79656c6c6f775d5b405261696e626f77405d5b626c75655d286d6f6f6e29000000000088401e44005869616f4a696d284e6577626965290000000000a33c13440047656f72676520436f7374616e7a610000000000af040e4400426f72696e6720426f620000000000b7600d440043687269730000000000b6fa0b44004e4c564e0000000000b0870a440044616e2052616d7061676500000000008d900144004b696b6572696e690000000000dae3ee43007761786f723132330000000000d713bf4300557a750000000000276b8f430063686577696e275f6d615f424943202346524545204b414b41524f5400000000000df78443004d696c6b205465612e00000000002a025a4300496e676c6f72696f75732042617374617264000000000052
feffffffff8400000201a4040e504300426c6f6f647920576f6c66000000000014583843005975724d756d0000000000f6452143004d6f64696361000000000041e7e242005b505d2e522e4f2e542e4f2e542e592e502e4500000000005db8da4200363940576172000000000018f00742
+263
View File
@@ -0,0 +1,263 @@
{
"anti_f2p_version": "2.1.0",
"backpack_tf_version": "2.11.1A",
"backpack_viewer_version": "1.1.2A",
"bethehorsemann_version": "1.1",
"connect_method_version": "1.2.0A",
"connect_version": "1.2.0",
"coop": "0",
"custom_chat_colors_mysql_version": "1.1.3A",
"custom_chat_colors_toggle_version": "2.0.0A",
"custom_chat_colors_version": "3.1.0A",
"deathmatch": "1",
"decalfrequency": "10",
"dynamicmotd_version": "2.2.3s",
"enhanced_items_version": "1.1.0A",
"falsemessages_version": "3.1",
"goto_version": "1.2",
"impersonate_version": "1.5.0",
"kartify_version": "1.5.0A",
"local_item_server_version": "1.1.5A",
"metamod_version": "1.10.6-devV",
"mp_allowNPCs": "1",
"mp_autocrosshair": "1",
"mp_autoteambalance": "0",
"mp_disable_respawn_times": "1",
"mp_fadetoblack": "0",
"mp_falldamage": "0",
"mp_flashlight": "0",
"mp_footsteps": "1",
"mp_forceautoteam": "0",
"mp_forcerespawn": "1",
"mp_fraglimit": "0",
"mp_friendlyfire": "0",
"mp_highlander": "0",
"mp_holiday_nogifts": "0",
"mp_match_end_at_timelimit": "1",
"mp_maxrounds": "0",
"mp_respawnwavetime": "10.0",
"mp_scrambleteams_auto": "0",
"mp_scrambleteams_auto_windifference": "3",
"mp_stalemate_enable": "0",
"mp_stalemate_meleeonly": "0",
"mp_teamlist": "hgrunt;scientist",
"mp_teamplay": "0",
"mp_timelimit": "300",
"mp_tournament": "0",
"mp_tournament_readymode": "0",
"mp_tournament_readymode_countdown": "10",
"mp_tournament_readymode_min": "2",
"mp_tournament_readymode_team_size": "0",
"mp_tournament_stopwatch": "1",
"mp_weaponstay": "0",
"mp_windifference": "0",
"mp_windifference_min": "0",
"mp_winlimit": "0",
"nextlevel": "",
"no_enemy_in_spawn_version": "1.2.1A",
"player_analytics_version": "1.3.1A",
"rainbowize_version": "1.7.0A",
"rplayer_version": "1.1",
"r_AirboatViewDampenDamp": "1.0",
"r_AirboatViewDampenFreq": "7.0",
"r_AirboatViewZHeight": "0.0",
"r_JeepViewDampenDamp": "1.0",
"r_JeepViewDampenFreq": "7.0",
"r_JeepViewZHeight": "10.0",
"r_VehicleViewDampen": "1",
"sbchecker_version": "1.0.2",
"sb_version": "1.4.11",
"scp_version": "2.1.0A",
"setuber_version": "1.3",
"smdj_version": "2.6.1",
"sm_adminsmite_version": "2.1",
"sm_aimnames_version": "0.8",
"sm_al_version": "1.0",
"sm_ammopackspawner_version": "1.0.0",
"sm_anticolorabuse_version": "1.0.0",
"sm_bgod_version": "1.0.1",
"sm_bhs_version": "1.0.0",
"sm_bleed_version": "1.0.0",
"sm_destroy_version": "1.2.0",
"sm_disco_version": "0.3.0",
"sm_fakegifts_version": "1.0.1",
"sm_fakeitem_version": "1.3.0",
"sm_fcvar_version": "1.1",
"sm_fia_version": "2.2.4",
"sm_funcommandsx_version": "2.2",
"sm_godmode_version": "2.3.1",
"sm_healthpack_spawner_version": "1.0.0",
"sm_horsemann_version": "1.1",
"sm_merasmus_version": "1.4.2",
"sm_monospawn_version": "1.1.1",
"sm_mutecheck_version": "1.9.2A",
"sm_nextmap": "trade_unusual_center_v3",
"sm_noisemaker_version": "2.2.0",
"sm_powerplay_version": "1.5.3m",
"sm_raffle_version": "0.9",
"sm_regen_version": "1.0",
"sm_resize_version": "1.2.0",
"sm_rweapons_version": "1.3",
"sm_setammo_version": "1.1.0",
"sm_setclass_chat": "1",
"sm_setclass_log": "1",
"sm_setclass_version": "1.2.0",
"sm_setspeed_chat": "1",
"sm_setspeed_log": "1",
"sm_setspeed_version": "1.3.1",
"sm_shutdown_countdown_version": "1.6.2A",
"sm_spray_version": "5.8a",
"sm_stunmod_version": "1.4.4.7",
"sm_taunt_version": "0.3",
"sm_tidychat_version": "0.4",
"sm_traderep_version": "1.0.2A",
"sm_updater_version": "1.2.2",
"sourcecomms_version": "0.9.266",
"sourcemod_version": "1.7.3-dev+5240",
"steamrep_checker_version": "1.2.0A",
"steamtools_version": "0.9.0+d5d0838",
"stripper_version": "1.2.2",
"st_gamedesc_override_version": "1.1.3A",
"sv_accelerate": "10",
"sv_airaccelerate": "10",
"sv_alltalk": "1",
"sv_bounce": "0",
"sv_cheats": "0",
"sv_contact": "service@firepoweredgaming.com",
"sv_footsteps": "1",
"sv_friction": "4",
"sv_gravity": "800",
"sv_maxspeed": "320",
"sv_maxusrcmdprocessticks": "24",
"sv_noclipaccelerate": "5",
"sv_noclipspeed": "5",
"sv_password": "0",
"sv_pausable": "0",
"sv_registration_message": "No account specified",
"sv_registration_successful": "0",
"sv_rollangle": "0",
"sv_rollspeed": "200",
"sv_specaccelerate": "5",
"sv_specnoclip": "1",
"sv_specspeed": "3",
"sv_steamgroup": "",
"sv_stepsize": "18",
"sv_stopspeed": "100",
"sv_tags": "FirePowered,alltalk,backpack.tf,increased_maxplayers,no_ads,noads,nopinion,norespawntime,trade,trading,unusual",
"sv_voiceenable": "1",
"sv_vote_quorum_ratio": "0.6",
"sv_wateraccelerate": "10",
"sv_waterfriction": "1",
"teamswitch_version": "1.3",
"tf2items_giveweapon_version": "3.14159",
"tf2items_manager": "1",
"tf2items_manager_version": "1.4.1",
"tf2items_version": "1.6.2",
"tfh_aprilfools": "0",
"tfh_birthday": "1",
"tfh_enabled": "1",
"tfh_endoftheline": "0",
"tfh_fullmoon": "1",
"tfh_halloween": "0",
"tfh_normalhealth": "0",
"tfh_valentines": "0",
"tfh_version": "1.10.2",
"tfh_winter": "1",
"tf_allow_player_use": "0",
"tf_arena_change_limit": "1",
"tf_arena_first_blood": "1",
"tf_arena_force_class": "0",
"tf_arena_max_streak": "3",
"tf_arena_override_cap_enable_time": "-1",
"tf_arena_preround_time": "10",
"tf_arena_round_time": "0",
"tf_arena_use_queue": "1",
"tf_beta_content": "0",
"tf_birthday": "0",
"tf_bot_count": "0",
"tf_classlimit": "0",
"tf_ctf_bonus_time": "10",
"tf_damage_disablespread": "1",
"tf_force_holidays_off": "0",
"tf_gamemode_arena": "0",
"tf_gamemode_cp": "0",
"tf_gamemode_ctf": "0",
"tf_gamemode_mvm": "0",
"tf_gamemode_passtime": "0",
"tf_gamemode_payload": "0",
"tf_gamemode_pd": "0",
"tf_gamemode_rd": "0",
"tf_gamemode_sd": "0",
"tf_max_charge_speed": "750",
"tf_medieval": "0",
"tf_medieval_autorp": "1",
"tf_mm_servermode": "1",
"tf_mm_strict": "0",
"tf_mm_trusted": "0",
"tf_mvm_death_penalty": "0",
"tf_mvm_min_players_to_start": "3",
"tf_overtime_nag": "0",
"tf_passtime_ball_carrier_regen_dmgtime": "3",
"tf_passtime_ball_carrier_regen_interval": "1",
"tf_passtime_ball_carrier_regen_maxpct": "1",
"tf_passtime_ball_carrier_regen_scale": "0.1f",
"tf_passtime_ball_damping_scale": "0.01f",
"tf_passtime_ball_drag_coefficient": "0.01f",
"tf_passtime_ball_inertia_scale": "1.0f",
"tf_passtime_ball_mass": "1.0f",
"tf_passtime_ball_model": "models/passtime/ball/passtime_ball.mdl",
"tf_passtime_ball_radius": "7.2f",
"tf_passtime_ball_reset_time": "15",
"tf_passtime_ball_rotdamping_scale": "1.0f",
"tf_passtime_ball_seek_range": "128",
"tf_passtime_ball_seek_speed_factor": "1.5f",
"tf_passtime_ball_takedamage": "1",
"tf_passtime_ball_takedamage_force": "800.0f",
"tf_passtime_flinch_boost": "0",
"tf_passtime_mode_homing_lock_sec": "1.5f",
"tf_passtime_mode_homing_speed": "1000.0f",
"tf_passtime_player_reticles_enemies": "1",
"tf_passtime_player_reticles_friends": "2",
"tf_passtime_score_crit_sec": "5.0f",
"tf_passtime_speedboost_on_get_ball_time": "2.0f",
"tf_passtime_steal_on_melee": "1",
"tf_passtime_teammate_steal_time": "45",
"tf_passtime_throwarc_demoman": "0.3f",
"tf_passtime_throwarc_engineer": "0.3f",
"tf_passtime_throwarc_heavy": "0.3f",
"tf_passtime_throwarc_medic": "0.3f",
"tf_passtime_throwarc_pyro": "0.3f",
"tf_passtime_throwarc_scout": "0.3f",
"tf_passtime_throwarc_sniper": "0.3f",
"tf_passtime_throwarc_soldier": "0.3f",
"tf_passtime_throwarc_spy": "0.3f",
"tf_passtime_throwspeed_demoman": "1000.0f",
"tf_passtime_throwspeed_engineer": "1000.0f",
"tf_passtime_throwspeed_heavy": "1000.0f",
"tf_passtime_throwspeed_medic": "1000.0f",
"tf_passtime_throwspeed_pyro": "1000.0f",
"tf_passtime_throwspeed_scout": "1000.0f",
"tf_passtime_throwspeed_sniper": "1000.0f",
"tf_passtime_throwspeed_soldier": "1000.0f",
"tf_passtime_throwspeed_spy": "1000.0f",
"tf_passtime_throwspeed_velocity_scale": "0",
"tf_playergib": "1",
"tf_player_name_change_time": "60",
"tf_powerup_mode": "0",
"tf_server_identity_disable_quickplay": "0",
"tf_spec_xray": "1",
"tf_spells_enabled": "0",
"tf_teamtalk": "1",
"tf_use_fixed_weaponspreads": "0",
"tf_weapon_criticals": "1",
"tf_weapon_criticals_melee": "1",
"thirdperson_version": "2.1.0",
"tidykick_version": "1.1.5A",
"tv_enable": "1",
"tv_password": "1",
"tv_relaypassword": "0",
"uberpunisher_version": "1.5.2",
"ufov_version": "1.2.0A",
"voiceannounce_ex_version": "2.0.0",
"votekick_switcher_version": "1.3.0A"
}
+6
View File
@@ -0,0 +1,6 @@
feffffff570100000600e004ffffffff450501616e74695f6632705f76657273696f6e00322e312e30006261636b7061636b5f74665f76657273696f6e00322e31312e3141006261636b7061636b5f7669657765725f76657273696f6e00312e312e3241006265746865686f7273656d616e6e5f76657273696f6e00312e3100636f6e6e6563745f6d6574686f645f76657273696f6e00312e322e304100636f6e6e6563745f76657273696f6e00312e322e3000636f6f70003000637573746f6d5f636861745f636f6c6f72735f6d7973716c5f76657273696f6e00312e312e334100637573746f6d5f636861745f636f6c6f72735f746f67676c655f76657273696f6e00322e302e304100637573746f6d5f636861745f636f6c6f72735f76657273696f6e00332e312e30410064656174686d61746368003100646563616c6672657175656e63790031300064796e616d69636d6f74645f76657273696f6e00322e322e337300656e68616e6365645f6974656d735f76657273696f6e00312e312e30410066616c73656d657373616765735f76657273696f6e00332e3100676f746f5f76657273696f6e00312e3200696d706572736f6e6174655f76657273696f6e00312e352e30006b6172746966795f76657273696f6e00312e352e3041006c6f63616c5f6974656d5f7365727665725f76657273696f6e00312e312e3541006d6574616d6f645f76657273696f6e00312e31302e362d64657656006d705f616c6c6f774e5043730031006d705f6175746f63726f7373686169720031006d705f6175746f7465616d62616c616e63650030006d705f64697361626c655f7265737061776e5f74696d65730031006d705f66616465746f626c61636b0030006d705f66616c6c64616d6167650030006d705f666c6173686c696768740030006d705f666f6f7473746570730031006d705f666f7263656175746f7465616d0030006d705f666f7263657265737061776e0031006d705f667261676c696d69740030006d705f667269656e646c79666972650030006d705f686967686c616e6465720030006d705f686f6c696461795f6e6f67696674730030006d705f6d617463685f656e645f61745f74696d656c696d69740031006d705f6d6178726f756e64730030006d705f7265737061776e7761766574696d650031302e30006d705f736372616d626c657465616d735f6175746f0030006d705f736372616d626c657465616d735f6175746f5f77696e646966666572656e63650033006d705f7374616c656d6174655f656e61626c650030006d705f7374616c656d6174655f6d656c65656f6e6c790030006d705f7465616d6c69737400686772756e743b736369656e74697374006d705f7465616d706c61790030006d705f74696d656c696d697400333030006d705f746f75726e616d656e740030006d705f746f75726e616d656e745f72656164796d6f64650030006d705f746f75726e616d656e745f72656164796d6f64655f636f756e74646f776e003130006d705f746f75726e616d656e745f72656164796d6f64655f6d696e0032006d705f746f75726e616d656e745f72656164796d6f64655f7465616d5f73697a650030006d705f746f75726e616d656e745f73746f7077617463680031006d705f776561706f6e737461790030006d705f77696e646966666572656e63650030006d705f77696e646966666572656e63655f6d696e0030
feffffff570100000601e004006d705f77696e6c696d69740030006e6578746c6576656c00006e6f5f656e656d795f696e5f737061776e5f76657273696f6e00312e322e314100706c617965725f616e616c79746963735f76657273696f6e00312e332e3141007261696e626f77697a655f76657273696f6e00312e372e30410072706c617965725f76657273696f6e00312e3100725f416972626f61745669657744616d70656e44616d7000312e3000725f416972626f61745669657744616d70656e4672657100372e3000725f416972626f6174566965775a48656967687400302e3000725f4a6565705669657744616d70656e44616d7000312e3000725f4a6565705669657744616d70656e4672657100372e3000725f4a656570566965775a4865696768740031302e3000725f56656869636c655669657744616d70656e0031007362636865636b65725f76657273696f6e00312e302e320073625f76657273696f6e00312e342e3131007363705f76657273696f6e00322e312e304100736574756265725f76657273696f6e00312e3300736d646a5f76657273696f6e00322e362e3100736d5f61646d696e736d6974655f76657273696f6e00322e3100736d5f61696d6e616d65735f76657273696f6e00302e3800736d5f616c5f76657273696f6e00312e3000736d5f616d6d6f7061636b737061776e65725f76657273696f6e00312e302e3000736d5f616e7469636f6c6f7261627573655f76657273696f6e00312e302e3000736d5f62676f645f76657273696f6e00312e302e3100736d5f6268735f76657273696f6e00312e302e3000736d5f626c6565645f76657273696f6e00312e302e3000736d5f64657374726f795f76657273696f6e00312e322e3000736d5f646973636f5f76657273696f6e00302e332e3000736d5f66616b6567696674735f76657273696f6e00312e302e3100736d5f66616b656974656d5f76657273696f6e00312e332e3000736d5f66637661725f76657273696f6e00312e3100736d5f6669615f76657273696f6e00322e322e3400736d5f66756e636f6d6d616e6473785f76657273696f6e00322e3200736d5f676f646d6f64655f76657273696f6e00322e332e3100736d5f6865616c74687061636b5f737061776e65725f76657273696f6e00312e302e3000736d5f686f7273656d616e6e5f76657273696f6e00312e3100736d5f6d657261736d75735f76657273696f6e00312e342e3200736d5f6d6f6e6f737061776e5f76657273696f6e00312e312e3100736d5f6d757465636865636b5f76657273696f6e00312e392e324100736d5f6e6578746d61700074726164655f756e757375616c5f63656e7465725f763300736d5f6e6f6973656d616b65725f76657273696f6e00322e322e3000736d5f706f776572706c61795f76657273696f6e00312e352e336d00736d5f726166666c655f76657273696f6e00302e3900736d5f726567656e5f76657273696f6e00312e3000736d5f726573697a655f76657273696f6e00312e322e3000736d5f72776561706f6e735f76657273696f6e00312e3300736d5f736574616d6d6f5f76657273696f6e00312e312e3000736d5f736574636c6173735f63686174003100736d5f736574636c6173735f6c6f67003100736d5f736574636c6173735f76657273696f6e00312e322e3000736d5f73657473706565645f63686174003100736d5f736574737065
feffffff570100000602e00465645f6c6f67003100736d5f73657473706565645f76657273696f6e00312e332e3100736d5f73687574646f776e5f636f756e74646f776e5f76657273696f6e00312e362e324100736d5f73707261795f76657273696f6e00352e386100736d5f7374756e6d6f645f76657273696f6e00312e342e342e3700736d5f7461756e745f76657273696f6e00302e3300736d5f74696479636861745f76657273696f6e00302e3400736d5f74726164657265705f76657273696f6e00312e302e324100736d5f757064617465725f76657273696f6e00312e322e3200736f75726365636f6d6d735f76657273696f6e00302e392e32363600736f757263656d6f645f76657273696f6e00312e372e332d6465762b3532343000737465616d7265705f636865636b65725f76657273696f6e00312e322e304100737465616d746f6f6c735f76657273696f6e00302e392e302b643564303833380073747269707065725f76657273696f6e00312e322e320073745f67616d65646573635f6f766572726964655f76657273696f6e00312e312e33410073765f616363656c65726174650031300073765f616972616363656c65726174650031300073765f616c6c74616c6b00310073765f626f756e636500300073765f63686561747300300073765f636f6e7461637400736572766963654066697265706f776572656467616d696e672e636f6d0073765f666f6f74737465707300310073765f6672696374696f6e00340073765f67726176697479003830300073765f6d61787370656564003332300073765f6d6178757372636d6470726f636573737469636b730032340073765f6e6f636c6970616363656c657261746500350073765f6e6f636c6970737065656400350073765f70617373776f726400300073765f7061757361626c6500300073765f726567697374726174696f6e5f6d657373616765004e6f206163636f756e74207370656369666965640073765f726567697374726174696f6e5f7375636365737366756c00300073765f726f6c6c616e676c6500300073765f726f6c6c7370656564003230300073765f73706563616363656c657261746500350073765f737065636e6f636c697000310073765f73706563737065656400330073765f737465616d67726f7570000073765f7374657073697a650031380073765f73746f707370656564003130300073765f746167730046697265506f77657265642c616c6c74616c6b2c6261636b7061636b2e74662c696e637265617365645f6d6178706c61796572732c6e6f5f6164732c6e6f6164732c6e6f70696e696f6e2c6e6f7265737061776e74696d652c74726164652c74726164696e672c756e757375616c0073765f766f696365656e61626c6500310073765f766f74655f71756f72756d5f726174696f00302e360073765f7761746572616363656c65726174650031300073765f77617465726672696374696f6e0031007465616d7377697463685f76657273696f6e00312e33007466326974656d735f67697665776561706f6e5f76657273696f6e00332e3134313539007466326974656d735f6d616e616765720031007466326974656d735f6d616e616765725f76657273696f6e00312e342e31007466326974656d735f76657273696f6e00312e362e32007466685f617072696c666f6f6c730030007466685f62697274686461790031007466685f65
feffffff570100000603e0046e61626c65640031007466685f656e646f667468656c696e650030007466685f66756c6c6d6f6f6e0031007466685f68616c6c6f7765656e0030007466685f6e6f726d616c6865616c74680030007466685f76616c656e74696e65730030007466685f76657273696f6e00312e31302e32007466685f77696e74657200310074665f616c6c6f775f706c617965725f75736500300074665f6172656e615f6368616e67655f6c696d697400310074665f6172656e615f66697273745f626c6f6f6400310074665f6172656e615f666f7263655f636c61737300300074665f6172656e615f6d61785f73747265616b00330074665f6172656e615f6f766572726964655f6361705f656e61626c655f74696d65002d310074665f6172656e615f707265726f756e645f74696d650031300074665f6172656e615f726f756e645f74696d6500300074665f6172656e615f7573655f717565756500310074665f626574615f636f6e74656e7400300074665f626972746864617900300074665f626f745f636f756e7400300074665f636c6173736c696d697400300074665f6374665f626f6e75735f74696d650031300074665f64616d6167655f64697361626c6573707265616400310074665f666f7263655f686f6c69646179735f6f666600300074665f67616d656d6f64655f6172656e6100300074665f67616d656d6f64655f637000300074665f67616d656d6f64655f63746600300074665f67616d656d6f64655f6d766d00300074665f67616d656d6f64655f7061737374696d6500300074665f67616d656d6f64655f7061796c6f616400300074665f67616d656d6f64655f706400300074665f67616d656d6f64655f726400300074665f67616d656d6f64655f736400300074665f6d61785f6368617267655f7370656564003735300074665f6d6564696576616c00300074665f6d6564696576616c5f6175746f727000310074665f6d6d5f7365727665726d6f646500310074665f6d6d5f73747269637400300074665f6d6d5f7472757374656400300074665f6d766d5f64656174685f70656e616c747900300074665f6d766d5f6d696e5f706c61796572735f746f5f737461727400330074665f6f76657274696d655f6e616700300074665f7061737374696d655f62616c6c5f636172726965725f726567656e5f646d6774696d6500330074665f7061737374696d655f62616c6c5f636172726965725f726567656e5f696e74657276616c00310074665f7061737374696d655f62616c6c5f636172726965725f726567656e5f6d617870637400310074665f7061737374696d655f62616c6c5f636172726965725f726567656e5f7363616c6500302e31660074665f7061737374696d655f62616c6c5f64616d70696e675f7363616c6500302e3031660074665f7061737374696d655f62616c6c5f647261675f636f656666696369656e7400302e3031660074665f7061737374696d655f62616c6c5f696e65727469615f7363616c6500312e30660074665f7061737374696d655f62616c6c5f6d61737300312e30660074665f7061737374696d655f62616c6c5f6d6f64656c006d6f64656c732f7061737374696d652f62616c6c2f7061737374696d655f62616c6c2e6d646c0074665f7061737374696d655f62616c6c5f72616469757300372e32660074665f7061737374696d655f62616c6c5f7265736574
feffffff570100000604e0045f74696d650031350074665f7061737374696d655f62616c6c5f726f7464616d70696e675f7363616c6500312e30660074665f7061737374696d655f62616c6c5f7365656b5f72616e6765003132380074665f7061737374696d655f62616c6c5f7365656b5f73706565645f666163746f7200312e35660074665f7061737374696d655f62616c6c5f74616b6564616d61676500310074665f7061737374696d655f62616c6c5f74616b6564616d6167655f666f726365003830302e30660074665f7061737374696d655f666c696e63685f626f6f737400300074665f7061737374696d655f6d6f64655f686f6d696e675f6c6f636b5f73656300312e35660074665f7061737374696d655f6d6f64655f686f6d696e675f737065656400313030302e30660074665f7061737374696d655f706c617965725f72657469636c65735f656e656d69657300310074665f7061737374696d655f706c617965725f72657469636c65735f667269656e647300320074665f7061737374696d655f73636f72655f637269745f73656300352e30660074665f7061737374696d655f7370656564626f6f73745f6f6e5f6765745f62616c6c5f74696d6500322e30660074665f7061737374696d655f737465616c5f6f6e5f6d656c656500310074665f7061737374696d655f7465616d6d6174655f737465616c5f74696d650034350074665f7061737374696d655f7468726f776172635f64656d6f6d616e00302e33660074665f7061737374696d655f7468726f776172635f656e67696e65657200302e33660074665f7061737374696d655f7468726f776172635f686561767900302e33660074665f7061737374696d655f7468726f776172635f6d6564696300302e33660074665f7061737374696d655f7468726f776172635f7079726f00302e33660074665f7061737374696d655f7468726f776172635f73636f757400302e33660074665f7061737374696d655f7468726f776172635f736e6970657200302e33660074665f7061737374696d655f7468726f776172635f736f6c6469657200302e33660074665f7061737374696d655f7468726f776172635f73707900302e33660074665f7061737374696d655f7468726f7773706565645f64656d6f6d616e00313030302e30660074665f7061737374696d655f7468726f7773706565645f656e67696e65657200313030302e30660074665f7061737374696d655f7468726f7773706565645f686561767900313030302e30660074665f7061737374696d655f7468726f7773706565645f6d6564696300313030302e30660074665f7061737374696d655f7468726f7773706565645f7079726f00313030302e30660074665f7061737374696d655f7468726f7773706565645f73636f757400313030302e30660074665f7061737374696d655f7468726f7773706565645f736e6970657200313030302e30660074665f7061737374696d655f7468726f7773706565645f736f6c6469657200313030302e30660074665f7061737374696d655f7468726f7773706565645f73707900313030302e30660074665f7061737374696d655f7468726f7773706565645f76656c6f636974795f7363616c6500300074665f706c6179657267696200310074665f706c617965725f6e616d655f6368616e67655f74696d650036300074665f706f77657275705f6d6f646500300074665f
feffffff570100000605e0047365727665725f6964656e746974795f64697361626c655f717569636b706c617900300074665f737065635f7872617900310074665f7370656c6c735f656e61626c656400300074665f7465616d74616c6b00310074665f7573655f66697865645f776561706f6e7370726561647300300074665f776561706f6e5f637269746963616c7300310074665f776561706f6e5f637269746963616c735f6d656c65650031007468697264706572736f6e5f76657273696f6e00322e312e3000746964796b69636b5f76657273696f6e00312e312e35410074765f656e61626c6500310074765f70617373776f726400310074765f72656c617970617373776f72640030007562657270756e69736865725f76657273696f6e00312e352e320075666f765f76657273696f6e00312e322e304100766f696365616e6e6f756e63655f65785f76657273696f6e00322e302e3000766f74656b69636b5f73776974636865725f76657273696f6e00312e332e304100
+324
View File
@@ -0,0 +1,324 @@
<?php
use PHPUnit\Framework\TestCase;
use xPaw\SourceQuery\BaseSocket;
use xPaw\SourceQuery\SourceQuery;
use xPaw\SourceQuery\Buffer;
class TestableSocket extends BaseSocket
{
private \SplQueue $PacketQueue;
public function __construct( )
{
$this->PacketQueue = new \SplQueue();
$this->PacketQueue->setIteratorMode( \SplDoublyLinkedList::IT_MODE_DELETE );
}
public function Queue( string $Data ) : void
{
$this->PacketQueue->push( $Data );
}
public function Close( ) : void
{
//
}
public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void
{
$this->Timeout = $Timeout;
$this->Engine = $Engine;
$this->Port = $Port;
$this->Address = $Address;
}
public function Write( int $Header, string $String = '' ) : bool
{
return true;
}
public function Read( int $Length = 1400 ) : Buffer
{
$Buffer = new Buffer( );
$Buffer->Set( (string)$this->PacketQueue->shift() );
$this->ReadInternal( $Buffer, $Length, [ $this, 'Sherlock' ] );
return $Buffer;
}
public function Sherlock( Buffer $Buffer, int $Length ) : bool
{
if( $this->PacketQueue->isEmpty() )
{
return false;
}
$Buffer->Set( (string)$this->PacketQueue->shift() );
return $Buffer->GetLong( ) === -2;
}
}
class Tests extends \PHPUnit\Framework\TestCase
{
private TestableSocket $Socket;
private SourceQuery $SourceQuery;
public function setUp() : void
{
$this->Socket = new TestableSocket();
$this->SourceQuery = new SourceQuery( $this->Socket );
$this->SourceQuery->Connect( '', 2 );
}
public function tearDown() : void
{
$this->SourceQuery->Disconnect();
unset( $this->Socket, $this->SourceQuery );
}
public function testInvalidTimeout() : void
{
$this->expectException( xPaw\SourceQuery\Exception\InvalidArgumentException::class );
$SourceQuery = new SourceQuery( );
$SourceQuery->Connect( '', 2, -1 );
}
public function testNotConnectedGetInfo() : void
{
$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
$this->SourceQuery->Disconnect();
$this->SourceQuery->GetInfo();
}
public function testNotConnectedPing() : void
{
$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
$this->SourceQuery->Disconnect();
$this->SourceQuery->Ping();
}
public function testNotConnectedGetPlayers() : void
{
$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
$this->SourceQuery->Disconnect();
$this->SourceQuery->GetPlayers();
}
/**
* @expectedException xPaw\SourceQuery\Exception\SocketException
*/
public function testNotConnectedGetRules() : void
{
$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
$this->SourceQuery->Disconnect();
$this->SourceQuery->GetRules();
}
public function testNotConnectedSetRconPassword() : void
{
$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
$this->SourceQuery->Disconnect();
$this->SourceQuery->SetRconPassword('a');
}
public function testNotConnectedRcon() : void
{
$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
$this->SourceQuery->Disconnect();
$this->SourceQuery->Rcon('a');
}
public function testRconWithoutPassword() : void
{
$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
$this->SourceQuery->Rcon('a');
}
/**
* @dataProvider InfoProvider
*/
public function testGetInfo( string $RawInput, array $ExpectedOutput ) : void
{
if( isset( $ExpectedOutput[ 'IsMod' ] ) )
{
$this->Socket->Engine = SourceQuery::GOLDSOURCE;
}
$this->Socket->Queue( $RawInput );
$RealOutput = $this->SourceQuery->GetInfo();
$this->assertEquals( $ExpectedOutput, $RealOutput );
}
public function InfoProvider() : array
{
$DataProvider = [];
$Files = glob( __DIR__ . '/Info/*.raw', GLOB_ERR );
foreach( $Files as $File )
{
$DataProvider[] =
[
hex2bin( trim( file_get_contents( $File ) ) ),
json_decode( file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
];
}
return $DataProvider;
}
/**
* @dataProvider BadPacketProvider
*/
public function testBadGetInfo( string $Data ) : void
{
$this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
$this->Socket->Queue( $Data );
$this->SourceQuery->GetInfo();
}
/**
* @dataProvider BadPacketProvider
*/
public function testBadGetChallengeViaPlayers( string $Data ) : void
{
$this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
$this->Socket->Queue( $Data );
$this->SourceQuery->GetPlayers();
}
/**
* @dataProvider BadPacketProvider
*/
public function testBadGetPlayersAfterCorrectChallenge( string $Data ) : void
{
$this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
$this->Socket->Queue( $Data );
$this->SourceQuery->GetPlayers();
}
/**
* @dataProvider BadPacketProvider
*/
public function testBadGetRulesAfterCorrectChallenge( string $Data ) : void
{
$this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
$this->Socket->Queue( $Data );
$this->SourceQuery->GetRules();
}
public function BadPacketProvider( ) : array
{
return
[
[ "" ],
[ "\xff\xff\xff\xff" ], // No type
[ "\xff\xff\xff\xff\x49" ], // Correct type, but no data after
[ "\xff\xff\xff\xff\x6D" ], // Old info packet, but tests are done for source
[ "\xff\xff\xff\xff\x11" ], // Wrong type
[ "\x11\x11\x11\x11" ], // Wrong header
[ "\xff" ], // Should be 4 bytes, but it's 1
];
}
public function testGetChallengeTwice( ) : void
{
$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x45\x01\x00ayy\x00lmao\x00" );
$this->assertEquals( [ 'ayy' => 'lmao' ], $this->SourceQuery->GetRules() );
$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x45\x01\x00wow\x00much\x00" );
$this->assertEquals( [ 'wow' => 'much' ], $this->SourceQuery->GetRules() );
}
/**
* @dataProvider RulesProvider
*/
public function testGetRules( array $RawInput, array $ExpectedOutput ) : void
{
$this->Socket->Queue( hex2bin( "ffffffff4104fce20e" ) ); // Challenge
foreach( $RawInput as $Packet )
{
$this->Socket->Queue( hex2bin( $Packet ) );
}
$RealOutput = $this->SourceQuery->GetRules();
$this->assertEquals( $ExpectedOutput, $RealOutput );
}
public function RulesProvider() : array
{
$DataProvider = [];
$Files = glob( __DIR__ . '/Rules/*.raw', GLOB_ERR );
foreach( $Files as $File )
{
$DataProvider[] =
[
file( $File, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES ),
json_decode( file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
];
}
return $DataProvider;
}
/**
* @dataProvider PlayersProvider
*/
public function testGetPlayers( array $RawInput, array $ExpectedOutput ) : void
{
$this->Socket->Queue( hex2bin( "ffffffff4104fce20e" ) ); // Challenge
foreach( $RawInput as $Packet )
{
$this->Socket->Queue( hex2bin( $Packet ) );
}
$RealOutput = $this->SourceQuery->GetPlayers();
$this->assertEquals( $ExpectedOutput, $RealOutput );
}
public function PlayersProvider() : array
{
$DataProvider = [];
$Files = glob( __DIR__ . '/Players/*.raw', GLOB_ERR );
foreach( $Files as $File )
{
$DataProvider[] =
[
file( $File, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES ),
json_decode( file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
];
}
return $DataProvider;
}
public function testPing() : void
{
$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x6A\x00");
$this->assertTrue( $this->SourceQuery->Ping() );
$this->Socket->Queue( "\xFF\xFF\xFF\xFF\xEE");
$this->assertFalse( $this->SourceQuery->Ping() );
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true"
bootstrap="../SourceQuery/bootstrap.php"
verbose="true">
<testsuites>
<testsuite name="Tests">
<file>./Tests.php</file>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../SourceQuery</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="../build/logs/clover.xml"/>
</logging>
</phpunit>
+16 -6
View File
@@ -8,18 +8,28 @@
[ [
"rcon", "rcon",
"minecraft", "minecraft",
"csgo",
"counter-strike", "counter-strike",
"team fortress" "team fortress",
"starbound",
"rust",
"ark",
"gmod"
], ],
"require": "require":
{ {
"php": ">=5.4" "php": ">=7.4"
},
"require-dev":
{
"phpunit/phpunit": "9.2",
"vimeo/psalm": "^3.12"
}, },
"autoload": "autoload":
{ {
"files": "psr-4":
[ {
"SourceQuery/SourceQuery.class.php" "xPaw\\SourceQuery\\": "SourceQuery/"
] }
} }
} }
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="SourceQuery" />
<directory name="Tests" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>