├── .gitattributes
├── .gitignore
├── Tests
├── Info
│ ├── hltv.raw
│ ├── theship.raw
│ ├── svencoop_nonsteam.raw
│ ├── gmod_cyrillic.raw
│ ├── csgo.raw
│ ├── hltv.json
│ ├── tf2.raw
│ ├── theship.json
│ ├── gmod_cyrillic.json
│ ├── csgo.json
│ ├── svencoop_nonsteam.json
│ └── tf2.json
├── phpunit.xml
├── Players
│ ├── csgo.raw
│ └── csgo.json
├── Tests.php
└── Rules
│ ├── tf2_sourcemod.json
│ └── tf2_sourcemod.raw
├── .editorconfig
├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── CONTRIBUTING.md
├── SourceQuery
├── Exception
│ ├── SourceQueryException.php
│ ├── InvalidArgumentException.php
│ ├── AuthenticationException.php
│ ├── SocketException.php
│ └── InvalidPacketException.php
├── BaseRcon.php
├── Socket.php
├── BaseSocket.php
├── GoldSourceRcon.php
├── Buffer.php
├── SourceRcon.php
└── SourceQuery.php
├── phpstan.neon
├── psalm.xml
├── Examples
├── RconExample.php
├── Example.php
└── View.php
├── composer.json
├── README.md
├── LICENSE
└── composer.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | Tests/.phpunit.cache/
3 |
--------------------------------------------------------------------------------
/Tests/Info/hltv.raw:
--------------------------------------------------------------------------------
1 | ffffffff6d3139322e3136382e312e3139373a323730323000436173746c65204d6f7274696d75733a30006f70345f6b626173650067656172626f7800484c5456000001307077000000
2 |
--------------------------------------------------------------------------------
/Tests/Info/theship.raw:
--------------------------------------------------------------------------------
1 | ffffffff4907524b537a6f6e652e636f6d207c205553204368696361676f207c205468652053686970207c2048756e74006174616c616e746100736869700054686520536869700060091b201064770001000205312e302e302e313600
2 |
--------------------------------------------------------------------------------
/Tests/Info/svencoop_nonsteam.raw:
--------------------------------------------------------------------------------
1 | ffffffff6d3132372e302e302e313a323730313500436c616e5343202333202d20456e67616765205b4c6f67726f735d0073635f646f63007376656e636f6f70005376656e20436f2d6f7020342e380000102f64770101000000010000000000000001000000
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = tab
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.yaml]
12 | indent_style = space
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/Tests/Info/gmod_cyrillic.raw:
--------------------------------------------------------------------------------
1 | ffffffff4911d093d0bed180d0bed0b420d098d0bdd0bdd0bed0b2d0b0d186d0b8d0b9207c20d0a0d183d181d181d0bad0b8d0b9204461726b52500072705f62616e67636c6177006761727279736d6f64004461726b525000a00f212800646c000131352e30382e313000b1876907403c286717400120676d3a6461726b727000a00f000000000000
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | ignore:
8 | - dependency-name: "*"
9 | update-types: ["version-update:semver-minor", "version-update:semver-patch"]
10 |
--------------------------------------------------------------------------------
/Tests/Info/csgo.raw:
--------------------------------------------------------------------------------
1 | ffffffff4911426f6d6247616d652062792078506177202620436f2e0064655f6475737432006373676f00436f756e7465722d537472696b653a20476c6f62616c204f6666656e7369766500da02001000646c0001312e33352e302e3700b19c6904e0eca764174001656d7074792c2a6772703a31313035333831692c626f6d6267616d652c73656375726500da02000000000000
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | * **This library is intended for software developers**, if you do not know how write code in PHP, please do not create issues.
2 | * 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.
3 |
--------------------------------------------------------------------------------
/Tests/Info/hltv.json:
--------------------------------------------------------------------------------
1 | {
2 | "Address": "192.168.1.197:27020",
3 | "HostName": "Castle Mortimus:0",
4 | "Map": "op4_kbase",
5 | "ModDir": "gearbox",
6 | "ModDesc": "HLTV",
7 | "Players": 0,
8 | "MaxPlayers": 1,
9 | "Protocol": 48,
10 | "Dedicated": "p",
11 | "Os": "w",
12 | "Password": false,
13 | "IsMod": false,
14 | "Secure": false,
15 | "Bots": 0
16 | }
17 |
--------------------------------------------------------------------------------
/SourceQuery/Exception/SourceQueryException.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ./Tests.php
11 |
12 |
13 |
14 |
15 |
16 | ../SourceQuery
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | permissions:
4 | contents: read
5 |
6 | on: [push]
7 |
8 | jobs:
9 | php:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | php: ['8.1', '8.2', '8.3', '8.4', '8.5']
14 |
15 | steps:
16 | - name: Setup PHP
17 | uses: shivammathur/setup-php@v2
18 | with:
19 | php-version: ${{ matrix.php }}
20 |
21 | - name: Checkout
22 | uses: actions/checkout@v4
23 |
24 | - name: Install dependencies
25 | run: composer install --no-interaction --no-progress
26 |
27 | - name: Run tests
28 | run: composer run phpunit
29 |
30 | - name: Run phpstan
31 | run: composer run phpstan
32 |
--------------------------------------------------------------------------------
/Tests/Info/tf2.json:
--------------------------------------------------------------------------------
1 | {
2 | "Protocol": 17,
3 | "HostName": " FirePowered.org | Unusual Trade | !jackpot",
4 | "Map": "trade_unusual_center_v3",
5 | "ModDir": "tf",
6 | "ModDesc": "Unusual Trading",
7 | "AppID": 440,
8 | "Players": 32,
9 | "MaxPlayers": 32,
10 | "Bots": 0,
11 | "Dedicated": "d",
12 | "Os": "l",
13 | "Password": false,
14 | "Secure": true,
15 | "Version": "3032525",
16 | "ExtraDataFlags": 241,
17 | "GamePort": 27045,
18 | "SteamID": 85568392920039468,
19 | "SpecPort": 27050,
20 | "SpecName": "ScamCam",
21 | "GameTags": "FirePowered,alltalk,backpack.tf,increased_maxplayers,no_ads,noads,nopinion,norespawntime,trade,trading,unusual",
22 | "GameID": 440
23 | }
24 |
--------------------------------------------------------------------------------
/SourceQuery/BaseRcon.php:
--------------------------------------------------------------------------------
1 |
13 | define( 'SQ_SERVER_ADDR', 'localhost' );
14 | define( 'SQ_SERVER_PORT', 27015 );
15 | define( 'SQ_TIMEOUT', 1 );
16 | define( 'SQ_ENGINE', SourceQuery::SOURCE );
17 | // Edit this <-
18 |
19 | $Query = new SourceQuery( );
20 |
21 | try
22 | {
23 | $Query->Connect( SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE );
24 |
25 | $Query->SetRconPassword( 'my_awesome_password' );
26 |
27 | var_dump( $Query->Rcon( 'say hello' ) );
28 | }
29 | catch( Exception $e )
30 | {
31 | echo $e->getMessage( );
32 | }
33 | finally
34 | {
35 | $Query->Disconnect( );
36 | }
37 |
--------------------------------------------------------------------------------
/Examples/Example.php:
--------------------------------------------------------------------------------
1 |
13 | define( 'SQ_SERVER_ADDR', 'localhost' );
14 | define( 'SQ_SERVER_PORT', 27015 );
15 | define( 'SQ_TIMEOUT', 1 );
16 | define( 'SQ_ENGINE', SourceQuery::SOURCE );
17 | // Edit this <-
18 |
19 | $Query = new SourceQuery( );
20 |
21 | try
22 | {
23 | $Query->Connect( SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE );
24 |
25 | print_r( $Query->GetInfo( ) );
26 | print_r( $Query->GetPlayers( ) );
27 | print_r( $Query->GetRules( ) );
28 | }
29 | catch( Exception $e )
30 | {
31 | echo $e->getMessage( );
32 | }
33 | finally
34 | {
35 | $Query->Disconnect( );
36 | }
37 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xpaw/php-source-query-class",
3 | "description": "PHP library to query and send RCON commands to servers based on \"Source Engine Query\" protocol",
4 | "homepage": "https://github.com/xPaw/PHP-Source-Query",
5 | "type": "library",
6 | "license": "LGPL-2.1",
7 | "keywords":
8 | [
9 | "rcon",
10 | "minecraft",
11 | "csgo",
12 | "counter-strike",
13 | "team fortress",
14 | "starbound",
15 | "rust",
16 | "ark",
17 | "gmod"
18 | ],
19 | "scripts":
20 | {
21 | "test":
22 | [
23 | "@phpunit",
24 | "@phpstan"
25 | ],
26 | "phpunit": "phpunit --configuration Tests/phpunit.xml --fail-on-warning",
27 | "phpstan": "phpstan"
28 | },
29 | "require":
30 | {
31 | "php": ">=8.1"
32 | },
33 | "require-dev":
34 | {
35 | "phpunit/phpunit": "^10.3",
36 | "phpstan/phpstan": "^2.0",
37 | "phpstan/phpstan-strict-rules": "^2.0"
38 | },
39 | "autoload":
40 | {
41 | "psr-4":
42 | {
43 | "xPaw\\SourceQuery\\": "SourceQuery/"
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Tests/Players/csgo.raw:
--------------------------------------------------------------------------------
1 | feffffffff8400000200a404ffffffff443a005a69656e0000000000d948344600efbca4efbd8fefbd86efbd86efbd9900000000007a5c0b4600e4b8b6e28691ebae88eca78020e8bf99e698afe4bb80e4b988e9acbc00000000003a920946004c6947476c655300000000005348ec4500e298a35b4147435d4d69636861656c204a2e204361626f6f7365e298a30000000000905ab145006465636973696f6e203c76616e636974795f64763e00000000004edfa645004d696d6520e29d9d4d617468656fe29d9e00000000001940864500537a70406e33720000000000898c724500466f7879207c20443a3c0000000000622c6a4500476967756c690000000000ebfb60450045726b78616c6c65000000000048c93245005b476c6f62616c456467656c6974655d4461433070236675636b7475726b73000000000087a5264500416c756d696e69750000000000c6ac244500426c61636b4c6f756e67652d5a6f6d626965534f4144205477696e42524f530000000000c6381c45004361707461696e204275737465720000000000ac4917450074686972747920736576656e00000000006eb315450021426173746172646f210000000000ed260e4500536169626f74e284a20000000000ae000c45004f4720572069206c206c20790000000000836508450054726958746572520000000000855afe4400546f6164734d616e73696f6e000000000003d1f644004d6f78736869e284a200000000000483f0440062697368000000000096efe444004172616b69736d6f00000000009769e244004a7573745f44616e6b00000000009d27db44004166746572736830636b00000000002680d7440053616962616d616e00000000009274cc440054686520556c74696d6174652047616d65720000000000934bc94400427574746572204b617961206e6f205065616e7574000000000075a0c144004c75676969610000000000fd02ba4400486f616e672042616f0000000000677aad4400456c646f205072657a6964656e746f00000000006612a84400536b795b432e592e525d0000000000e758a3440050756e636b43686f7000000000007bee784400456c6d6f72616469616e00000000008e0b724400667269656e64736869707a000000000090d37144004775617264696f7300000000008e4a60440053616d75656c204c757a0000000000cbdb4244004c6f72644d65726b780000000000c9123d44007a65726f796a6b000000000083a92e44005b79656c6c6f775d5b405261696e626f77405d5b626c75655d286d6f6f6e29000000000088401e44005869616f4a696d284e6577626965290000000000a33c13440047656f72676520436f7374616e7a610000000000af040e4400426f72696e6720426f620000000000b7600d440043687269730000000000b6fa0b44004e4c564e0000000000b0870a440044616e2052616d7061676500000000008d900144004b696b6572696e690000000000dae3ee43007761786f723132330000000000d713bf4300557a750000000000276b8f430063686577696e275f6d615f424943202346524545204b414b41524f5400000000000df78443004d696c6b205465612e00000000002a025a4300496e676c6f72696f75732042617374617264000000000052
2 | feffffffff8400000201a4040e504300426c6f6f647920576f6c66000000000014583843005975724d756d0000000000f6452143004d6f64696361000000000041e7e242005b505d2e522e4f2e542e4f2e542e592e502e4500000000005db8da4200363940576172000000000018f00742
3 |
--------------------------------------------------------------------------------
/SourceQuery/Socket.php:
--------------------------------------------------------------------------------
1 | Socket ) )
28 | {
29 | fclose( $this->Socket );
30 |
31 | $this->Socket = null;
32 | }
33 | }
34 |
35 | public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void
36 | {
37 | $this->Timeout = $Timeout;
38 | $this->Engine = $Engine;
39 | $this->Port = $Port;
40 | $this->Address = $Address;
41 |
42 | $Socket = @fsockopen( 'udp://' . $Address, $Port, $ErrNo, $ErrStr, $Timeout );
43 |
44 | if( $ErrNo || $Socket === false )
45 | {
46 | throw new SocketException( 'Could not create socket: ' . $ErrStr, SocketException::COULD_NOT_CREATE_SOCKET );
47 | }
48 |
49 | $this->Socket = $Socket;
50 | stream_set_timeout( $this->Socket, $Timeout );
51 | stream_set_blocking( $this->Socket, true );
52 | }
53 |
54 | public function Write( int $Header, string $String = '' ) : bool
55 | {
56 | if( $this->Socket === null )
57 | {
58 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
59 | }
60 |
61 | $Command = pack( 'ccccca*', 0xFF, 0xFF, 0xFF, 0xFF, $Header, $String );
62 | $Length = strlen( $Command );
63 |
64 | return $Length === fwrite( $this->Socket, $Command, $Length );
65 | }
66 |
67 | private const MaxPacketLength = 1 << 16;
68 |
69 | /**
70 | * Reads from socket and returns Buffer.
71 | *
72 | * @throws InvalidPacketException
73 | */
74 | public function Read( ) : Buffer
75 | {
76 | if( $this->Socket === null )
77 | {
78 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
79 | }
80 |
81 | $Data = fread( $this->Socket, self::MaxPacketLength );
82 | $Buffer = new Buffer( );
83 | $Buffer->Set( $Data === false ? '' : $Data );
84 |
85 | $this->ReadInternal( $Buffer, [ $this, 'Sherlock' ] );
86 |
87 | return $Buffer;
88 | }
89 |
90 | public function Sherlock( Buffer $Buffer ) : bool
91 | {
92 | if( $this->Socket === null )
93 | {
94 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
95 | }
96 |
97 | $Data = fread( $this->Socket, self::MaxPacketLength );
98 |
99 | if( $Data === false || strlen( $Data ) < 4 )
100 | {
101 | return false;
102 | }
103 |
104 | $Buffer->Set( $Data );
105 |
106 | return $Buffer->ReadInt32( ) === -2;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/SourceQuery/BaseSocket.php:
--------------------------------------------------------------------------------
1 | Close( );
36 | }
37 |
38 | abstract public function Close( ) : void;
39 | abstract public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void;
40 | abstract public function Write( int $Header, string $String = '' ) : bool;
41 | abstract public function Read( ) : Buffer;
42 |
43 | protected function ReadInternal( Buffer $Buffer, callable $SherlockFunction ) : Buffer
44 | {
45 | if( $Buffer->Remaining( ) === 0 )
46 | {
47 | throw new InvalidPacketException( 'Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
48 | }
49 |
50 | $Header = $Buffer->ReadInt32( );
51 |
52 | if( $Header === -1 ) // Single packet
53 | {
54 | // We don't have to do anything
55 | }
56 | else if( $Header === -2 ) // Split packet
57 | {
58 | $Packets = [];
59 | $IsCompressed = false;
60 | $ReadMore = false;
61 | $PacketChecksum = null;
62 |
63 | do
64 | {
65 | $RequestID = $Buffer->ReadInt32( );
66 | $PacketCount = 0;
67 | $PacketNumber = 0;
68 |
69 | switch( $this->Engine )
70 | {
71 | case SourceQuery::GOLDSOURCE:
72 | {
73 | $PacketCountAndNumber = $Buffer->ReadByte( );
74 | $PacketCount = $PacketCountAndNumber & 0xF;
75 | $PacketNumber = $PacketCountAndNumber >> 4;
76 |
77 | break;
78 | }
79 | case SourceQuery::SOURCE:
80 | {
81 | $IsCompressed = ( $RequestID & 0x80000000 ) !== 0;
82 | $PacketCount = $Buffer->ReadByte( );
83 | $PacketNumber = $Buffer->ReadByte( ) + 1;
84 |
85 | if( $IsCompressed )
86 | {
87 | $Buffer->ReadInt32( ); // Split size
88 |
89 | $PacketChecksum = $Buffer->ReadUInt32( );
90 | }
91 | else
92 | {
93 | $Buffer->ReadInt16( ); // Split size
94 | }
95 |
96 | break;
97 | }
98 | default:
99 | {
100 | throw new SocketException( 'Unknown engine.', SocketException::INVALID_ENGINE );
101 | }
102 | }
103 |
104 | $Packets[ $PacketNumber ] = $Buffer->Read( );
105 |
106 | $ReadMore = $PacketCount > sizeof( $Packets );
107 | }
108 | while( $ReadMore && $SherlockFunction( $Buffer ) );
109 |
110 | $Data = implode( $Packets );
111 |
112 | // TODO: Test this
113 | if( $IsCompressed )
114 | {
115 | // Let's make sure this function exists, it's not included in PHP by default
116 | if( !function_exists( 'bzdecompress' ) )
117 | {
118 | throw new \RuntimeException( 'Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.' );
119 | }
120 |
121 | $Data = bzdecompress( $Data );
122 |
123 | if( !is_string( $Data ) || crc32( $Data ) !== $PacketChecksum )
124 | {
125 | throw new InvalidPacketException( 'CRC32 checksum mismatch of uncompressed packet data.', InvalidPacketException::CHECKSUM_MISMATCH );
126 | }
127 | }
128 |
129 | $Buffer->Set( substr( $Data, 4 ) );
130 | }
131 | else
132 | {
133 | throw new InvalidPacketException( 'Socket read: Raw packet header mismatch. (0x' . dechex( $Header ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
134 | }
135 |
136 | return $Buffer;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/SourceQuery/GoldSourceRcon.php:
--------------------------------------------------------------------------------
1 | Socket = $Socket;
37 | }
38 |
39 | public function Close( ) : void
40 | {
41 | $this->RconChallenge = '';
42 | $this->RconPassword = '';
43 | }
44 |
45 | public function Open( ) : void
46 | {
47 | //
48 | }
49 |
50 | public function Write( int $Header, string $String = '' ) : bool
51 | {
52 | if( $this->Socket->Socket === null )
53 | {
54 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
55 | }
56 |
57 | $Command = pack( 'cccca*', 0xFF, 0xFF, 0xFF, 0xFF, $String );
58 | $Length = strlen( $Command );
59 |
60 | return $Length === fwrite( $this->Socket->Socket, $Command, $Length );
61 | }
62 |
63 | /**
64 | * @throws AuthenticationException
65 | */
66 | public function Read( ) : Buffer
67 | {
68 | // GoldSource RCON has same structure as Query
69 | $Buffer = $this->Socket->Read( );
70 |
71 | $StringBuffer = '';
72 | $ReadMore = false;
73 |
74 | // There is no indentifier of the end, so we just need to continue reading
75 | do
76 | {
77 | $ReadMore = $Buffer->Remaining( ) > 0;
78 |
79 | if( $ReadMore )
80 | {
81 | if( $Buffer->ReadByte( ) !== SourceQuery::S2A_RCON )
82 | {
83 | throw new InvalidPacketException( 'Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH );
84 | }
85 |
86 | $Packet = $Buffer->Read( );
87 | $StringBuffer .= $Packet;
88 | //$StringBuffer .= SubStr( $Packet, 0, -2 );
89 |
90 | // Let's assume if this packet is not long enough, there are no more after this one
91 | $ReadMore = strlen( $Packet ) > 1000; // use 1300?
92 |
93 | if( $ReadMore )
94 | {
95 | $Buffer = $this->Socket->Read( );
96 | }
97 | }
98 | }
99 | while( $ReadMore );
100 |
101 | $Trimmed = trim( $StringBuffer );
102 |
103 | if( $Trimmed === 'Bad rcon_password.' )
104 | {
105 | throw new AuthenticationException( $Trimmed, AuthenticationException::BAD_PASSWORD );
106 | }
107 | else if( $Trimmed === 'You have been banned from this server.' )
108 | {
109 | throw new AuthenticationException( $Trimmed, AuthenticationException::BANNED );
110 | }
111 |
112 | $Buffer->Set( $Trimmed );
113 |
114 | return $Buffer;
115 | }
116 |
117 | public function Command( string $Command ) : string
118 | {
119 | if( !$this->RconChallenge )
120 | {
121 | throw new AuthenticationException( 'Tried to execute a RCON command before successful authorization.', AuthenticationException::BAD_PASSWORD );
122 | }
123 |
124 | $this->Write( 0, 'rcon ' . $this->RconChallenge . ' "' . $this->RconPassword . '" ' . $Command . "\0" );
125 | $Buffer = $this->Read( );
126 |
127 | return $Buffer->Read( );
128 | }
129 |
130 | public function Authorize( string $Password ) : void
131 | {
132 | $this->RconPassword = $Password;
133 |
134 | $this->Write( 0, 'challenge rcon' );
135 | $Buffer = $this->Socket->Read( );
136 |
137 | if( $Buffer->Read( 14 ) !== 'challenge rcon' )
138 | {
139 | throw new AuthenticationException( 'Failed to get RCON challenge.', AuthenticationException::BAD_PASSWORD );
140 | }
141 |
142 | $this->RconChallenge = trim( $Buffer->Read( ) );
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/SourceQuery/Buffer.php:
--------------------------------------------------------------------------------
1 | Buffer = $Buffer;
45 | $this->Length = strlen( $Buffer );
46 | $this->Position = 0;
47 | }
48 |
49 | /**
50 | * Get remaining bytes
51 | *
52 | * @return int Remaining bytes in buffer
53 | *
54 | * @phpstan-impure
55 | */
56 | public function Remaining( ) : int
57 | {
58 | return $this->Length - $this->Position;
59 | }
60 |
61 | /**
62 | * Reads the specified number of bytes.
63 | *
64 | * @param int $Length Bytes to read
65 | */
66 | public function Read( int $Length = -1 ) : string
67 | {
68 | if( $Length === 0 )
69 | {
70 | return '';
71 | }
72 |
73 | $Remaining = $this->Remaining( );
74 |
75 | if( $Length === -1 )
76 | {
77 | $Length = $Remaining;
78 | }
79 | else if( $Length > $Remaining )
80 | {
81 | return '';
82 | }
83 |
84 | $Data = substr( $this->Buffer, $this->Position, $Length );
85 |
86 | $this->Position += $Length;
87 |
88 | return $Data;
89 | }
90 |
91 | /**
92 | * Reads the next byte.
93 | */
94 | public function ReadByte( ) : int
95 | {
96 | return ord( $this->Read( 1 ) );
97 | }
98 |
99 | /**
100 | * Reads a 2-byte signed integer.
101 | */
102 | public function ReadInt16( ) : int
103 | {
104 | if( $this->Remaining( ) < 2 )
105 | {
106 | throw new InvalidPacketException( 'Not enough data to unpack.', InvalidPacketException::BUFFER_EMPTY );
107 | }
108 |
109 | $Data = unpack( 'v', $this->Read( 2 ) );
110 |
111 | if( $Data === false )
112 | {
113 | throw new InvalidPacketException( 'Failed to unpack.', InvalidPacketException::UNPACK_FAILED );
114 | }
115 |
116 | return (int)$Data[ 1 ];
117 | }
118 |
119 | /**
120 | * Reads a 4-byte signed integer.
121 | */
122 | public function ReadInt32( ) : int
123 | {
124 | if( $this->Remaining( ) < 4 )
125 | {
126 | throw new InvalidPacketException( 'Not enough data to unpack.', InvalidPacketException::BUFFER_EMPTY );
127 | }
128 |
129 | $Data = unpack( 'l', $this->Read( 4 ) );
130 |
131 | if( $Data === false )
132 | {
133 | throw new InvalidPacketException( 'Failed to unpack.', InvalidPacketException::UNPACK_FAILED );
134 | }
135 |
136 | return (int)$Data[ 1 ];
137 | }
138 |
139 | /**
140 | * Reads a 4-byte floating point value.
141 | */
142 | public function ReadFloat32( ) : float
143 | {
144 | if( $this->Remaining( ) < 4 )
145 | {
146 | throw new InvalidPacketException( 'Not enough data to unpack.', InvalidPacketException::BUFFER_EMPTY );
147 | }
148 |
149 | $Data = unpack( 'f', $this->Read( 4 ) );
150 |
151 | if( $Data === false )
152 | {
153 | throw new InvalidPacketException( 'Failed to unpack.', InvalidPacketException::UNPACK_FAILED );
154 | }
155 |
156 | return (float)$Data[ 1 ];
157 | }
158 |
159 | /**
160 | * Reads a 4-byte unsigned integer.
161 | */
162 | public function ReadUInt32( ) : int
163 | {
164 | if( $this->Remaining( ) < 4 )
165 | {
166 | throw new InvalidPacketException( 'Not enough data to unpack.', InvalidPacketException::BUFFER_EMPTY );
167 | }
168 |
169 | $Data = unpack( 'V', $this->Read( 4 ) );
170 |
171 | if( $Data === false )
172 | {
173 | throw new InvalidPacketException( 'Failed to unpack.', InvalidPacketException::UNPACK_FAILED );
174 | }
175 |
176 | return (int)$Data[ 1 ];
177 | }
178 |
179 | /**
180 | * Read a null-terminated string.
181 | */
182 | public function ReadNullTermString( ) : string
183 | {
184 | $ZeroBytePosition = strpos( $this->Buffer, "\0", $this->Position );
185 |
186 | if( $ZeroBytePosition === false )
187 | {
188 | return '';
189 | }
190 |
191 | $String = $this->Read( $ZeroBytePosition - $this->Position );
192 |
193 | $this->Position++;
194 |
195 | return $String;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/Examples/View.php:
--------------------------------------------------------------------------------
1 |
9 | define( 'SQ_SERVER_ADDR', 'localhost' );
10 | define( 'SQ_SERVER_PORT', 27015 );
11 | define( 'SQ_TIMEOUT', 3 );
12 | define( 'SQ_ENGINE', SourceQuery::SOURCE );
13 | // Edit this <-
14 |
15 | $Timer = microtime( true );
16 |
17 | $Query = new SourceQuery( );
18 |
19 | $Info = null;
20 | $Rules = [];
21 | $Players = [];
22 | $Exception = null;
23 |
24 | try
25 | {
26 | $Query->Connect( SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE );
27 | //$Query->SetUseOldGetChallengeMethod( true ); // Use this when players/rules retrieval fails on games like Starbound
28 |
29 | $Info = $Query->GetInfo( );
30 | $Players = $Query->GetPlayers( );
31 | $Rules = $Query->GetRules( );
32 | }
33 | catch( Exception $e )
34 | {
35 | $Exception = $e;
36 | }
37 | finally
38 | {
39 | $Query->Disconnect( );
40 | }
41 |
42 | $Timer = number_format( microtime( true ) - $Timer, 4, '.', '' );
43 | ?>
44 |
45 |
46 |
47 |
48 | Source Query PHP Library
49 |
50 |
51 |
75 |
76 |
77 |
78 |
79 |
80 |
Source Query PHP Library
81 |
82 |
This library was created to query game server which use the Source (Steamworks) query protocol.
83 |
84 |
85 | Made by xPaw
86 | View on GitHub
87 | LGPL v2.1
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
__toString( ) ); ?>
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | | Server Info |
104 | s |
105 |
106 |
107 |
108 |
109 | $InfoValue ): ?>
110 |
111 | |
112 | ";
116 | print_r( $InfoValue );
117 | echo "";
118 | }
119 | else
120 | {
121 | if( $InfoValue === true )
122 | {
123 | echo 'true';
124 | }
125 | else if( $InfoValue === false )
126 | {
127 | echo 'false';
128 | }
129 | else
130 | {
131 | echo htmlspecialchars( (string)$InfoValue );
132 | }
133 | }
134 | ?> |
135 |
136 |
137 |
138 |
139 | | No information received |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | | Player |
150 | Frags |
151 | Time |
152 |
153 |
154 |
155 | 0 ): ?>
156 |
157 |
158 | |
159 | |
160 | |
161 |
162 |
163 |
164 |
165 | | No players received |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 | | Rules |
178 |
179 |
180 |
181 | 0 ): ?>
182 | $Value ): ?>
183 |
184 | |
185 | |
186 |
187 |
188 |
189 |
190 | | No rules received |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/SourceQuery/SourceRcon.php:
--------------------------------------------------------------------------------
1 | Socket = $Socket;
38 | }
39 |
40 | public function Close( ) : void
41 | {
42 | if( $this->RconSocket )
43 | {
44 | fclose( $this->RconSocket );
45 |
46 | $this->RconSocket = null;
47 | }
48 |
49 | $this->RconRequestId = 0;
50 | }
51 |
52 | public function Open( ) : void
53 | {
54 | if( !$this->RconSocket )
55 | {
56 | $RconSocket = @fsockopen( $this->Socket->Address, $this->Socket->Port, $ErrNo, $ErrStr, $this->Socket->Timeout );
57 |
58 | if( $ErrNo || !$RconSocket )
59 | {
60 | throw new SocketException( 'Can\'t connect to RCON server: ' . $ErrStr, SocketException::CONNECTION_FAILED );
61 | }
62 |
63 | $this->RconSocket = $RconSocket;
64 | stream_set_timeout( $this->RconSocket, $this->Socket->Timeout );
65 | stream_set_blocking( $this->RconSocket, true );
66 | }
67 | }
68 |
69 | public function Write( int $Header, string $String = '' ) : bool
70 | {
71 | if( $this->RconSocket === null )
72 | {
73 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
74 | }
75 |
76 | // Pack the packet together
77 | $Command = pack( 'VV', ++$this->RconRequestId, $Header ) . $String . "\x00\x00";
78 |
79 | // Prepend packet length
80 | $Command = pack( 'V', strlen( $Command ) ) . $Command;
81 | $Length = strlen( $Command );
82 |
83 | return $Length === fwrite( $this->RconSocket, $Command, $Length );
84 | }
85 |
86 | public function Read( ) : Buffer
87 | {
88 | if( $this->RconSocket === null )
89 | {
90 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
91 | }
92 |
93 | $Data = fread( $this->RconSocket, 4 );
94 | $Buffer = new Buffer( );
95 | $Buffer->Set( $Data === false ? '' : $Data );
96 |
97 | if( $Buffer->Remaining( ) < 4 )
98 | {
99 | throw new InvalidPacketException( 'Rcon read: Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
100 | }
101 |
102 | $PacketSize = $Buffer->ReadInt32( );
103 |
104 | if( $PacketSize <= 0 )
105 | {
106 | throw new InvalidPacketException( 'Rcon read: Packet size was empty', InvalidPacketException::BUFFER_EMPTY );
107 | }
108 |
109 | $Data = fread( $this->RconSocket, $PacketSize );
110 | $Buffer->Set( $Data === false ? '' : $Data );
111 |
112 | $Data = $Buffer->Read( );
113 |
114 | $Remaining = $PacketSize - strlen( $Data );
115 |
116 | while( $Remaining > 0 )
117 | {
118 | $Data2 = fread( $this->RconSocket, $Remaining );
119 |
120 | if( $Data2 === false || strlen( $Data2 ) === 0 )
121 | {
122 | throw new InvalidPacketException( 'Read ' . strlen( $Data ) . ' bytes from socket, ' . $Remaining . ' remaining', InvalidPacketException::BUFFER_EMPTY );
123 | }
124 |
125 | $Data .= $Data2;
126 | $Remaining -= strlen( $Data2 );
127 | }
128 |
129 | $Buffer->Set( $Data );
130 |
131 | return $Buffer;
132 | }
133 |
134 | public function Command( string $Command ) : string
135 | {
136 | $this->Write( SourceQuery::SERVERDATA_EXECCOMMAND, $Command );
137 | $Buffer = $this->Read( );
138 |
139 | $Buffer->ReadInt32( ); // RequestID
140 |
141 | $Type = $Buffer->ReadInt32( );
142 |
143 | if( $Type === SourceQuery::SERVERDATA_AUTH_RESPONSE )
144 | {
145 | throw new AuthenticationException( 'Bad rcon_password.', AuthenticationException::BAD_PASSWORD );
146 | }
147 | else if( $Type !== SourceQuery::SERVERDATA_RESPONSE_VALUE )
148 | {
149 | throw new InvalidPacketException( 'Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH );
150 | }
151 |
152 | $Data = $Buffer->Read( );
153 |
154 | // We do this stupid hack to handle split packets
155 | // See https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses
156 | if( strlen( $Data ) >= 4000 )
157 | {
158 | $this->Write( SourceQuery::SERVERDATA_REQUESTVALUE );
159 |
160 | do
161 | {
162 | $Buffer = $this->Read( );
163 |
164 | $Buffer->ReadInt32( ); // RequestID
165 |
166 | if( $Buffer->ReadInt32( ) !== SourceQuery::SERVERDATA_RESPONSE_VALUE )
167 | {
168 | break;
169 | }
170 |
171 | $Data2 = $Buffer->Read( );
172 |
173 | if( $Data2 === "\x00\x01\x00\x00\x00\x00" )
174 | {
175 | break;
176 | }
177 |
178 | $Data .= $Data2;
179 | }
180 | while( true );
181 | }
182 |
183 | return rtrim( $Data, "\0" );
184 | }
185 |
186 | public function Authorize( string $Password ) : void
187 | {
188 | $this->Write( SourceQuery::SERVERDATA_AUTH, $Password );
189 | $Buffer = $this->Read( );
190 |
191 | $RequestID = $Buffer->ReadInt32( );
192 | $Type = $Buffer->ReadInt32( );
193 |
194 | // If we receive SERVERDATA_RESPONSE_VALUE, then we need to read again
195 | // More info: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Additional_Comments
196 |
197 | if( $Type === SourceQuery::SERVERDATA_RESPONSE_VALUE )
198 | {
199 | $Buffer = $this->Read( );
200 |
201 | $RequestID = $Buffer->ReadInt32( );
202 | $Type = $Buffer->ReadInt32( );
203 | }
204 |
205 | if( $RequestID === -1 || $Type !== SourceQuery::SERVERDATA_AUTH_RESPONSE )
206 | {
207 | throw new AuthenticationException( 'RCON authorization failed.', AuthenticationException::BAD_PASSWORD );
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHP Source Query
2 |
3 | [](https://packagist.org/packages/xpaw/php-source-query-class)
4 | [](https://packagist.org/packages/xpaw/php-source-query-class)
5 |
6 | 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.
7 |
8 | The class also allows you to query servers using RCON although this only works for half-life 1 and source engine games.
9 |
10 | [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.
11 |
12 | **:warning: Do not send me emails if this does not work for you, I will not help you.**
13 |
14 | ## Requirements
15 | * [Modern PHP version](https://php.net/supported-versions.php)
16 | * 64-bit PHP or [gmp module](https://secure.php.net/manual/en/book.gmp.php)
17 | * Your server must allow UDP connections
18 |
19 | ## Protocol Specifications
20 | * https://developer.valvesoftware.com/wiki/Server_queries
21 | * https://developer.valvesoftware.com/wiki/Source_RCON_Protocol
22 |
23 | ## Supported Games
24 | AppID | Game | Query | RCON | Notes
25 | ----- | ---- | :---: | :--: | ----
26 | ~ | All HL1/HL2 games and mods | :white_check_mark: | :white_check_mark: |
27 | 10 | [Counter-Strike 1.6](http://store.steampowered.com/app/10/) | :white_check_mark: | :white_check_mark: |
28 | 440 | [Team Fortress 2](http://store.steampowered.com/app/440/) | :white_check_mark: | :white_check_mark: |
29 | 550 | [Left 4 Dead 2](http://store.steampowered.com/app/550/) | :white_check_mark: | :white_check_mark: |
30 | 730 | [Counter-Strike 2](http://store.steampowered.com/app/730/) | :white_check_mark: | :white_check_mark: | `host_name_store 1; host_info_show 2; host_players_show 2`
31 | 1002 | [Rag Doll Kung Fu](http://store.steampowered.com/app/1002/) | :white_check_mark: | :white_check_mark: |
32 | 2400 | [The Ship](http://store.steampowered.com/app/2400/) | :white_check_mark: | :white_check_mark: |
33 | 4000 | [Garry's Mod](http://store.steampowered.com/app/4000/) | :white_check_mark: | :white_check_mark: |
34 | 17710 | [Nuclear Dawn](http://store.steampowered.com/app/17710/) | :white_check_mark: | :white_check_mark: |
35 | 70000 | [Dino D-Day](http://store.steampowered.com/app/70000/) | :white_check_mark: | :white_check_mark: |
36 | 107410 | [Arma 3](http://store.steampowered.com/app/107410/) | :white_check_mark: | :x: | Add +1 to the server port
37 | 115300 | [Call of Duty: Modern Warfare 3](http://store.steampowered.com/app/115300/) | :white_check_mark: | :white_check_mark: |
38 | 162107 | [DeadPoly](https://store.steampowered.com/app/1621070/) | :white_check_mark: | :x: |
39 | 211820 | [Starbound](http://store.steampowered.com/app/211820/) | :white_check_mark: | :white_check_mark: | Call `SetUseOldGetChallengeMethod` method after connecting
40 | 244850 | [Space Engineers](http://store.steampowered.com/app/244850/) | :white_check_mark: | :x: | Add +1 to the server port
41 | 304930 | [Unturned](https://store.steampowered.com/app/304930/) | :white_check_mark: | :x: | Add +1 to the server port
42 | 251570 | [7 Days to Die](http://store.steampowered.com/app/251570) | :white_check_mark: | :x: |
43 | 252490 | [Rust](http://store.steampowered.com/app/252490/) | :white_check_mark: | :x: |
44 | 282440 | [Quake Live](http://store.steampowered.com/app/282440) | :white_check_mark: | :x: | Quake Live uses the ZMQ messaging queue protocol for rcon control.
45 | 346110 | [ARK: Survival Evolved](http://store.steampowered.com/app/346110/) | :white_check_mark: | :white_check_mark: |
46 | ~ | [Minecraft](http://www.minecraft.net/) | :x: | :white_check_mark: |
47 | 108600 | [Project: Zomboid](https://store.steampowered.com/app/108600/) | :white_check_mark: | :white_check_mark:
48 |
49 | Open a pull request if you know another game which supports Source Query and/or RCON protocols.
50 |
51 | ## How to tell if the game supports Source Query Protocol?
52 |
53 | Add your server to your favourites in Steam server browser, and if Steam can display information about your server, then the protocol is supported.
54 |
55 | ## Functions
56 |
57 |
58 | | Connect( $Ip, $Port, $Timeout, $Engine ) |
59 | Opens connection to a server |
60 |
61 |
62 | | Disconnect( ) |
63 | Closes all open connections |
64 |
65 |
66 | | Ping( ) |
67 | Ping the server to see if it exists Warning: Source engine may not answer to this |
68 |
69 |
70 | | GetInfo( ) |
71 | Returns server info in an array |
72 |
73 |
74 | | GetPlayers( ) |
75 | Returns players on the server in an array |
76 |
77 |
78 | | GetRules( ) |
79 | Returns public rules (cvars) in an array |
80 |
81 |
82 | | SetRconPassword( $Password ) |
83 | Sets rcon password for later use with Rcon() |
84 |
85 |
86 | | Rcon( $Command ) |
87 | Execute rcon command on the server |
88 |
89 |
90 |
91 | Also refer to [examples folder](Examples/) to work things out.
92 |
93 | ## License
94 | PHP Source Query
95 | Copyright (C) 2012-2025 Pavel Djundik
96 |
97 | This library is free software; you can redistribute it and/or
98 | modify it under the terms of the GNU Lesser General Public
99 | License as published by the Free Software Foundation; either
100 | version 2.1 of the License, or (at your option) any later version.
101 |
102 | This library is distributed in the hope that it will be useful,
103 | but WITHOUT ANY WARRANTY; without even the implied warranty of
104 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
105 | Lesser General Public License for more details.
106 |
107 | You should have received a copy of the GNU Lesser General Public
108 | License along with this library; if not, write to the Free Software
109 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
110 |
--------------------------------------------------------------------------------
/Tests/Players/csgo.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Id": 0,
4 | "Name": "Zien",
5 | "Frags": 0,
6 | "Time": 11538,
7 | "TimeF": "03:12:18"
8 | },
9 | {
10 | "Id": 0,
11 | "Name": "Doffy",
12 | "Frags": 0,
13 | "Time": 8919,
14 | "TimeF": "02:28:39"
15 | },
16 | {
17 | "Id": 0,
18 | "Name": "丶↑뮈지 这是什么鬼",
19 | "Frags": 0,
20 | "Time": 8804,
21 | "TimeF": "02:26:44"
22 | },
23 | {
24 | "Id": 0,
25 | "Name": "LiGGleS",
26 | "Frags": 0,
27 | "Time": 7561,
28 | "TimeF": "02:06:01"
29 | },
30 | {
31 | "Id": 0,
32 | "Name": "☣[AGC]Michael J. Caboose☣",
33 | "Frags": 0,
34 | "Time": 5675,
35 | "TimeF": "01:34:35"
36 | },
37 | {
38 | "Id": 0,
39 | "Name": "decision ",
40 | "Frags": 0,
41 | "Time": 5339,
42 | "TimeF": "01:28:59"
43 | },
44 | {
45 | "Id": 0,
46 | "Name": "Mime ❝Matheo❞",
47 | "Frags": 0,
48 | "Time": 4296,
49 | "TimeF": "01:11:36"
50 | },
51 | {
52 | "Id": 0,
53 | "Name": "Szp@n3r",
54 | "Frags": 0,
55 | "Time": 3880,
56 | "TimeF": "01:04:40"
57 | },
58 | {
59 | "Id": 0,
60 | "Name": "Foxy | D:<",
61 | "Frags": 0,
62 | "Time": 3746,
63 | "TimeF": "01:02:26"
64 | },
65 | {
66 | "Id": 0,
67 | "Name": "Giguli",
68 | "Frags": 0,
69 | "Time": 3599,
70 | "TimeF": "59:59"
71 | },
72 | {
73 | "Id": 0,
74 | "Name": "Erkxalle",
75 | "Frags": 0,
76 | "Time": 2860,
77 | "TimeF": "47:40"
78 | },
79 | {
80 | "Id": 0,
81 | "Name": "[GlobalEdgelite]DaC0p#fuckturks",
82 | "Frags": 0,
83 | "Time": 2666,
84 | "TimeF": "44:26"
85 | },
86 | {
87 | "Id": 0,
88 | "Name": "Aluminiu",
89 | "Frags": 0,
90 | "Time": 2634,
91 | "TimeF": "43:54"
92 | },
93 | {
94 | "Id": 0,
95 | "Name": "BlackLounge-ZombieSOAD TwinBROS",
96 | "Frags": 0,
97 | "Time": 2499,
98 | "TimeF": "41:39"
99 | },
100 | {
101 | "Id": 0,
102 | "Name": "Captain Buster",
103 | "Frags": 0,
104 | "Time": 2420,
105 | "TimeF": "40:20"
106 | },
107 | {
108 | "Id": 0,
109 | "Name": "thirty seven",
110 | "Frags": 0,
111 | "Time": 2395,
112 | "TimeF": "39:55"
113 | },
114 | {
115 | "Id": 0,
116 | "Name": "!Bastardo!",
117 | "Frags": 0,
118 | "Time": 2274,
119 | "TimeF": "37:54"
120 | },
121 | {
122 | "Id": 0,
123 | "Name": "Saibot™",
124 | "Frags": 0,
125 | "Time": 2240,
126 | "TimeF": "37:20"
127 | },
128 | {
129 | "Id": 0,
130 | "Name": "OG W i l l y",
131 | "Frags": 0,
132 | "Time": 2182,
133 | "TimeF": "36:22"
134 | },
135 | {
136 | "Id": 0,
137 | "Name": "TriXterR",
138 | "Frags": 0,
139 | "Time": 2034,
140 | "TimeF": "33:54"
141 | },
142 | {
143 | "Id": 0,
144 | "Name": "ToadsMansion",
145 | "Frags": 0,
146 | "Time": 1974,
147 | "TimeF": "32:54"
148 | },
149 | {
150 | "Id": 0,
151 | "Name": "Moxshi™",
152 | "Frags": 0,
153 | "Time": 1924,
154 | "TimeF": "32:04"
155 | },
156 | {
157 | "Id": 0,
158 | "Name": "bish",
159 | "Frags": 0,
160 | "Time": 1831,
161 | "TimeF": "30:31"
162 | },
163 | {
164 | "Id": 0,
165 | "Name": "Arakismo",
166 | "Frags": 0,
167 | "Time": 1811,
168 | "TimeF": "30:11"
169 | },
170 | {
171 | "Id": 0,
172 | "Name": "Just_Dank",
173 | "Frags": 0,
174 | "Time": 1753,
175 | "TimeF": "29:13"
176 | },
177 | {
178 | "Id": 0,
179 | "Name": "Aftersh0ck",
180 | "Frags": 0,
181 | "Time": 1724,
182 | "TimeF": "28:44"
183 | },
184 | {
185 | "Id": 0,
186 | "Name": "Saibaman",
187 | "Frags": 0,
188 | "Time": 1635,
189 | "TimeF": "27:15"
190 | },
191 | {
192 | "Id": 0,
193 | "Name": "The Ultimate Gamer",
194 | "Frags": 0,
195 | "Time": 1610,
196 | "TimeF": "26:50"
197 | },
198 | {
199 | "Id": 0,
200 | "Name": "Butter Kaya no Peanut",
201 | "Frags": 0,
202 | "Time": 1549,
203 | "TimeF": "25:49"
204 | },
205 | {
206 | "Id": 0,
207 | "Name": "Lugiia",
208 | "Frags": 0,
209 | "Time": 1488,
210 | "TimeF": "24:48"
211 | },
212 | {
213 | "Id": 0,
214 | "Name": "Hoang Bao",
215 | "Frags": 0,
216 | "Time": 1387,
217 | "TimeF": "23:07"
218 | },
219 | {
220 | "Id": 0,
221 | "Name": "Eldo Prezidento",
222 | "Frags": 0,
223 | "Time": 1344,
224 | "TimeF": "22:24"
225 | },
226 | {
227 | "Id": 0,
228 | "Name": "Sky[C.Y.R]",
229 | "Frags": 0,
230 | "Time": 1306,
231 | "TimeF": "21:46"
232 | },
233 | {
234 | "Id": 0,
235 | "Name": "PunckChop",
236 | "Frags": 0,
237 | "Time": 995,
238 | "TimeF": "16:35"
239 | },
240 | {
241 | "Id": 0,
242 | "Name": "Elmoradian",
243 | "Frags": 0,
244 | "Time": 968,
245 | "TimeF": "16:08"
246 | },
247 | {
248 | "Id": 0,
249 | "Name": "friendshipz",
250 | "Frags": 0,
251 | "Time": 967,
252 | "TimeF": "16:07"
253 | },
254 | {
255 | "Id": 0,
256 | "Name": "Guardios",
257 | "Frags": 0,
258 | "Time": 897,
259 | "TimeF": "14:57"
260 | },
261 | {
262 | "Id": 0,
263 | "Name": "Samuel Luz",
264 | "Frags": 0,
265 | "Time": 779,
266 | "TimeF": "12:59"
267 | },
268 | {
269 | "Id": 0,
270 | "Name": "LordMerkx",
271 | "Frags": 0,
272 | "Time": 756,
273 | "TimeF": "12:36"
274 | },
275 | {
276 | "Id": 0,
277 | "Name": "zeroyjk",
278 | "Frags": 0,
279 | "Time": 698,
280 | "TimeF": "11:38"
281 | },
282 | {
283 | "Id": 0,
284 | "Name": "[yellow][@Rainbow@][blue](moon)",
285 | "Frags": 0,
286 | "Time": 633,
287 | "TimeF": "10:33"
288 | },
289 | {
290 | "Id": 0,
291 | "Name": "XiaoJim(Newbie)",
292 | "Frags": 0,
293 | "Time": 588,
294 | "TimeF": "09:48"
295 | },
296 | {
297 | "Id": 0,
298 | "Name": "George Costanza",
299 | "Frags": 0,
300 | "Time": 568,
301 | "TimeF": "09:28"
302 | },
303 | {
304 | "Id": 0,
305 | "Name": "Boring Bob",
306 | "Frags": 0,
307 | "Time": 565,
308 | "TimeF": "09:25"
309 | },
310 | {
311 | "Id": 0,
312 | "Name": "Chris",
313 | "Frags": 0,
314 | "Time": 559,
315 | "TimeF": "09:19"
316 | },
317 | {
318 | "Id": 0,
319 | "Name": "NLVN",
320 | "Frags": 0,
321 | "Time": 554,
322 | "TimeF": "09:14"
323 | },
324 | {
325 | "Id": 0,
326 | "Name": "Dan Rampage",
327 | "Frags": 0,
328 | "Time": 518,
329 | "TimeF": "08:38"
330 | },
331 | {
332 | "Id": 0,
333 | "Name": "Kikerini",
334 | "Frags": 0,
335 | "Time": 477,
336 | "TimeF": "07:57"
337 | },
338 | {
339 | "Id": 0,
340 | "Name": "waxor123",
341 | "Frags": 0,
342 | "Time": 382,
343 | "TimeF": "06:22"
344 | },
345 | {
346 | "Id": 0,
347 | "Name": "Uzu",
348 | "Frags": 0,
349 | "Time": 286,
350 | "TimeF": "04:46"
351 | },
352 | {
353 | "Id": 0,
354 | "Name": "chewin'_ma_BIC #FREE KAKAROT",
355 | "Frags": 0,
356 | "Time": 265,
357 | "TimeF": "04:25"
358 | },
359 | {
360 | "Id": 0,
361 | "Name": "Milk Tea.",
362 | "Frags": 0,
363 | "Time": 218,
364 | "TimeF": "03:38"
365 | },
366 | {
367 | "Id": 0,
368 | "Name": "Inglorious Bastard",
369 | "Frags": 0,
370 | "Time": 208,
371 | "TimeF": "03:28"
372 | },
373 | {
374 | "Id": 0,
375 | "Name": "Bloody Wolf",
376 | "Frags": 0,
377 | "Time": 184,
378 | "TimeF": "03:04"
379 | },
380 | {
381 | "Id": 0,
382 | "Name": "YurMum",
383 | "Frags": 0,
384 | "Time": 161,
385 | "TimeF": "02:41"
386 | },
387 | {
388 | "Id": 0,
389 | "Name": "Modica",
390 | "Frags": 0,
391 | "Time": 113,
392 | "TimeF": "01:53"
393 | },
394 | {
395 | "Id": 0,
396 | "Name": "[P].R.O.T.O.T.Y.P.E",
397 | "Frags": 0,
398 | "Time": 109,
399 | "TimeF": "01:49"
400 | },
401 | {
402 | "Id": 0,
403 | "Name": "69@War",
404 | "Frags": 0,
405 | "Time": 33,
406 | "TimeF": "00:33"
407 | }
408 | ]
409 |
--------------------------------------------------------------------------------
/Tests/Tests.php:
--------------------------------------------------------------------------------
1 | */
11 | private \SplQueue $PacketQueue;
12 |
13 | public function __construct( )
14 | {
15 | $this->PacketQueue = new \SplQueue();
16 | $this->PacketQueue->setIteratorMode( \SplDoublyLinkedList::IT_MODE_DELETE );
17 |
18 | }
19 |
20 | public function Queue( string $Data ) : void
21 | {
22 | $this->PacketQueue->push( $Data );
23 | }
24 |
25 | public function Close( ) : void
26 | {
27 | //
28 | }
29 |
30 | public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void
31 | {
32 | $this->Timeout = $Timeout;
33 | $this->Engine = $Engine;
34 | $this->Port = $Port;
35 | $this->Address = $Address;
36 | }
37 |
38 | public function Write( int $Header, string $String = '' ) : bool
39 | {
40 | return true;
41 | }
42 |
43 | public function Read( ) : Buffer
44 | {
45 | $Buffer = new Buffer( );
46 | $Buffer->Set( $this->PacketQueue->shift() );
47 |
48 | $this->ReadInternal( $Buffer, [ $this, 'Sherlock' ] );
49 |
50 | return $Buffer;
51 | }
52 |
53 | public function Sherlock( Buffer $Buffer ) : bool
54 | {
55 | if( $this->PacketQueue->isEmpty() )
56 | {
57 | return false;
58 | }
59 |
60 | $Buffer->Set( $this->PacketQueue->shift() );
61 |
62 | return $Buffer->ReadInt32( ) === -2;
63 | }
64 | }
65 |
66 | class Tests extends \PHPUnit\Framework\TestCase
67 | {
68 | private TestableSocket $Socket;
69 | private SourceQuery $SourceQuery;
70 |
71 | public function setUp() : void
72 | {
73 | $this->Socket = new TestableSocket();
74 | $this->SourceQuery = new SourceQuery( $this->Socket );
75 | $this->SourceQuery->Connect( '', 2 );
76 | }
77 |
78 | public function tearDown() : void
79 | {
80 | $this->SourceQuery->Disconnect();
81 |
82 | unset( $this->Socket, $this->SourceQuery );
83 | }
84 |
85 | public function testInvalidTimeout() : void
86 | {
87 | $this->expectException( xPaw\SourceQuery\Exception\InvalidArgumentException::class );
88 | $SourceQuery = new SourceQuery( );
89 | $SourceQuery->Connect( '', 2, -1 );
90 | }
91 |
92 | public function testNotConnectedGetInfo() : void
93 | {
94 | $this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
95 | $this->SourceQuery->Disconnect();
96 | $this->SourceQuery->GetInfo();
97 | }
98 |
99 | public function testNotConnectedPing() : void
100 | {
101 | $this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
102 | $this->SourceQuery->Disconnect();
103 | $this->SourceQuery->Ping();
104 | }
105 |
106 | public function testNotConnectedGetPlayers() : void
107 | {
108 | $this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
109 | $this->SourceQuery->Disconnect();
110 | $this->SourceQuery->GetPlayers();
111 | }
112 |
113 | /**
114 | * @expectedException xPaw\SourceQuery\Exception\SocketException
115 | */
116 | public function testNotConnectedGetRules() : void
117 | {
118 | $this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
119 | $this->SourceQuery->Disconnect();
120 | $this->SourceQuery->GetRules();
121 | }
122 |
123 | public function testNotConnectedSetRconPassword() : void
124 | {
125 | $this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
126 | $this->SourceQuery->Disconnect();
127 | $this->SourceQuery->SetRconPassword('a');
128 | }
129 |
130 | public function testNotConnectedRcon() : void
131 | {
132 | $this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
133 | $this->SourceQuery->Disconnect();
134 | $this->SourceQuery->Rcon('a');
135 | }
136 |
137 | public function testRconWithoutPassword() : void
138 | {
139 | $this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
140 | $this->SourceQuery->Rcon('a');
141 | }
142 |
143 | /**
144 | * @dataProvider InfoProvider
145 | */
146 | public function testGetInfo( string $RawInput, array $ExpectedOutput ) : void
147 | {
148 | if( isset( $ExpectedOutput[ 'IsMod' ] ) )
149 | {
150 | $this->Socket->Engine = SourceQuery::GOLDSOURCE;
151 | }
152 |
153 | $this->Socket->Queue( $RawInput );
154 |
155 | $RealOutput = $this->SourceQuery->GetInfo();
156 |
157 | self::assertEquals( $ExpectedOutput, $RealOutput );
158 | }
159 |
160 | public static function InfoProvider() : array
161 | {
162 | $DataProvider = [];
163 |
164 | $Files = glob( __DIR__ . '/Info/*.raw', GLOB_ERR );
165 |
166 | if( $Files === false )
167 | {
168 | throw new Exception();
169 | }
170 |
171 | foreach( $Files as $File )
172 | {
173 | $DataProvider[] =
174 | [
175 | hex2bin( trim( (string)file_get_contents( $File ) ) ),
176 | json_decode( (string)file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
177 | ];
178 | }
179 |
180 | return $DataProvider;
181 | }
182 |
183 | /**
184 | * @dataProvider BadPacketProvider
185 | */
186 | public function testBadGetInfo( string $Data ) : void
187 | {
188 | $this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
189 | $this->Socket->Queue( $Data );
190 |
191 | $this->SourceQuery->GetInfo();
192 | }
193 |
194 | /**
195 | * @dataProvider BadPacketProvider
196 | */
197 | public function testBadGetChallengeViaPlayers( string $Data ) : void
198 | {
199 | $this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
200 | $this->Socket->Queue( $Data );
201 |
202 | $this->SourceQuery->GetPlayers();
203 | }
204 |
205 | /**
206 | * @dataProvider BadPacketProvider
207 | */
208 | public function testBadGetPlayersAfterCorrectChallenge( string $Data ) : void
209 | {
210 | $this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
211 | $this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
212 | $this->Socket->Queue( $Data );
213 |
214 | $this->SourceQuery->GetPlayers();
215 | }
216 |
217 | /**
218 | * @dataProvider BadPacketProvider
219 | */
220 | public function testBadGetRulesAfterCorrectChallenge( string $Data ) : void
221 | {
222 | $this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
223 | $this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
224 | $this->Socket->Queue( $Data );
225 |
226 | $this->SourceQuery->GetRules();
227 | }
228 |
229 | public static function BadPacketProvider( ) : array
230 | {
231 | return
232 | [
233 | [ "" ],
234 | [ "\xff\xff\xff\xff" ], // No type
235 | [ "\xff\xff\xff\xff\x49" ], // Correct type, but no data after
236 | [ "\xff\xff\xff\xff\x6D" ], // Old info packet, but tests are done for source
237 | [ "\xff\xff\xff\xff\x11" ], // Wrong type
238 | [ "\x11\x11\x11\x11" ], // Wrong header
239 | [ "\xff" ], // Should be 4 bytes, but it's 1
240 | ];
241 | }
242 |
243 | public function testGetChallengeTwice( ) : void
244 | {
245 | $this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
246 | $this->Socket->Queue( "\xFF\xFF\xFF\xFF\x45\x01\x00ayy\x00lmao\x00" );
247 | self::assertEquals( [ 'ayy' => 'lmao' ], $this->SourceQuery->GetRules() );
248 |
249 | $this->Socket->Queue( "\xFF\xFF\xFF\xFF\x45\x01\x00wow\x00much\x00" );
250 | self::assertEquals( [ 'wow' => 'much' ], $this->SourceQuery->GetRules() );
251 | }
252 |
253 | /**
254 | * @dataProvider RulesProvider
255 | * @param array $RawInput
256 | */
257 | public function testGetRules( array $RawInput, array $ExpectedOutput ) : void
258 | {
259 | $this->Socket->Queue( (string)hex2bin( "ffffffff4104fce20e" ) ); // Challenge
260 |
261 | foreach( $RawInput as $Packet )
262 | {
263 | $this->Socket->Queue( (string)hex2bin( $Packet ) );
264 | }
265 |
266 | $RealOutput = $this->SourceQuery->GetRules();
267 |
268 | self::assertEquals( $ExpectedOutput, $RealOutput );
269 | }
270 |
271 | public static function RulesProvider() : array
272 | {
273 | $DataProvider = [];
274 |
275 | $Files = glob( __DIR__ . '/Rules/*.raw', GLOB_ERR );
276 |
277 | if( $Files === false )
278 | {
279 | throw new Exception();
280 | }
281 |
282 | foreach( $Files as $File )
283 | {
284 | $DataProvider[] =
285 | [
286 | file( $File, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES ),
287 | json_decode( (string)file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
288 | ];
289 | }
290 |
291 | return $DataProvider;
292 | }
293 |
294 | /**
295 | * @dataProvider PlayersProvider
296 | * @param array $RawInput
297 | */
298 | public function testGetPlayers( array $RawInput, array $ExpectedOutput ) : void
299 | {
300 | $this->Socket->Queue( (string)hex2bin( "ffffffff4104fce20e" ) ); // Challenge
301 |
302 | foreach( $RawInput as $Packet )
303 | {
304 | $this->Socket->Queue( (string)hex2bin( $Packet ) );
305 | }
306 |
307 | $RealOutput = $this->SourceQuery->GetPlayers();
308 |
309 | self::assertEquals( $ExpectedOutput, $RealOutput );
310 | }
311 |
312 | public static function PlayersProvider() : array
313 | {
314 | $DataProvider = [];
315 |
316 | $Files = glob( __DIR__ . '/Players/*.raw', GLOB_ERR );
317 |
318 | if( $Files === false )
319 | {
320 | throw new Exception();
321 | }
322 |
323 | foreach( $Files as $File )
324 | {
325 | $DataProvider[] =
326 | [
327 | file( $File, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES ),
328 | json_decode( (string)file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
329 | ];
330 | }
331 |
332 | return $DataProvider;
333 | }
334 |
335 | public function testPing() : void
336 | {
337 | $this->Socket->Queue( "\xFF\xFF\xFF\xFF\x6A\x00");
338 | self::assertTrue( $this->SourceQuery->Ping() );
339 |
340 | $this->Socket->Queue( "\xFF\xFF\xFF\xFF\xEE");
341 | self::assertFalse( $this->SourceQuery->Ping() );
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/Tests/Rules/tf2_sourcemod.json:
--------------------------------------------------------------------------------
1 | {
2 | "anti_f2p_version": "2.1.0",
3 | "backpack_tf_version": "2.11.1A",
4 | "backpack_viewer_version": "1.1.2A",
5 | "bethehorsemann_version": "1.1",
6 | "connect_method_version": "1.2.0A",
7 | "connect_version": "1.2.0",
8 | "coop": "0",
9 | "custom_chat_colors_mysql_version": "1.1.3A",
10 | "custom_chat_colors_toggle_version": "2.0.0A",
11 | "custom_chat_colors_version": "3.1.0A",
12 | "deathmatch": "1",
13 | "decalfrequency": "10",
14 | "dynamicmotd_version": "2.2.3s",
15 | "enhanced_items_version": "1.1.0A",
16 | "falsemessages_version": "3.1",
17 | "goto_version": "1.2",
18 | "impersonate_version": "1.5.0",
19 | "kartify_version": "1.5.0A",
20 | "local_item_server_version": "1.1.5A",
21 | "metamod_version": "1.10.6-devV",
22 | "mp_allowNPCs": "1",
23 | "mp_autocrosshair": "1",
24 | "mp_autoteambalance": "0",
25 | "mp_disable_respawn_times": "1",
26 | "mp_fadetoblack": "0",
27 | "mp_falldamage": "0",
28 | "mp_flashlight": "0",
29 | "mp_footsteps": "1",
30 | "mp_forceautoteam": "0",
31 | "mp_forcerespawn": "1",
32 | "mp_fraglimit": "0",
33 | "mp_friendlyfire": "0",
34 | "mp_highlander": "0",
35 | "mp_holiday_nogifts": "0",
36 | "mp_match_end_at_timelimit": "1",
37 | "mp_maxrounds": "0",
38 | "mp_respawnwavetime": "10.0",
39 | "mp_scrambleteams_auto": "0",
40 | "mp_scrambleteams_auto_windifference": "3",
41 | "mp_stalemate_enable": "0",
42 | "mp_stalemate_meleeonly": "0",
43 | "mp_teamlist": "hgrunt;scientist",
44 | "mp_teamplay": "0",
45 | "mp_timelimit": "300",
46 | "mp_tournament": "0",
47 | "mp_tournament_readymode": "0",
48 | "mp_tournament_readymode_countdown": "10",
49 | "mp_tournament_readymode_min": "2",
50 | "mp_tournament_readymode_team_size": "0",
51 | "mp_tournament_stopwatch": "1",
52 | "mp_weaponstay": "0",
53 | "mp_windifference": "0",
54 | "mp_windifference_min": "0",
55 | "mp_winlimit": "0",
56 | "nextlevel": "",
57 | "no_enemy_in_spawn_version": "1.2.1A",
58 | "player_analytics_version": "1.3.1A",
59 | "rainbowize_version": "1.7.0A",
60 | "rplayer_version": "1.1",
61 | "r_AirboatViewDampenDamp": "1.0",
62 | "r_AirboatViewDampenFreq": "7.0",
63 | "r_AirboatViewZHeight": "0.0",
64 | "r_JeepViewDampenDamp": "1.0",
65 | "r_JeepViewDampenFreq": "7.0",
66 | "r_JeepViewZHeight": "10.0",
67 | "r_VehicleViewDampen": "1",
68 | "sbchecker_version": "1.0.2",
69 | "sb_version": "1.4.11",
70 | "scp_version": "2.1.0A",
71 | "setuber_version": "1.3",
72 | "smdj_version": "2.6.1",
73 | "sm_adminsmite_version": "2.1",
74 | "sm_aimnames_version": "0.8",
75 | "sm_al_version": "1.0",
76 | "sm_ammopackspawner_version": "1.0.0",
77 | "sm_anticolorabuse_version": "1.0.0",
78 | "sm_bgod_version": "1.0.1",
79 | "sm_bhs_version": "1.0.0",
80 | "sm_bleed_version": "1.0.0",
81 | "sm_destroy_version": "1.2.0",
82 | "sm_disco_version": "0.3.0",
83 | "sm_fakegifts_version": "1.0.1",
84 | "sm_fakeitem_version": "1.3.0",
85 | "sm_fcvar_version": "1.1",
86 | "sm_fia_version": "2.2.4",
87 | "sm_funcommandsx_version": "2.2",
88 | "sm_godmode_version": "2.3.1",
89 | "sm_healthpack_spawner_version": "1.0.0",
90 | "sm_horsemann_version": "1.1",
91 | "sm_merasmus_version": "1.4.2",
92 | "sm_monospawn_version": "1.1.1",
93 | "sm_mutecheck_version": "1.9.2A",
94 | "sm_nextmap": "trade_unusual_center_v3",
95 | "sm_noisemaker_version": "2.2.0",
96 | "sm_powerplay_version": "1.5.3m",
97 | "sm_raffle_version": "0.9",
98 | "sm_regen_version": "1.0",
99 | "sm_resize_version": "1.2.0",
100 | "sm_rweapons_version": "1.3",
101 | "sm_setammo_version": "1.1.0",
102 | "sm_setclass_chat": "1",
103 | "sm_setclass_log": "1",
104 | "sm_setclass_version": "1.2.0",
105 | "sm_setspeed_chat": "1",
106 | "sm_setspeed_log": "1",
107 | "sm_setspeed_version": "1.3.1",
108 | "sm_shutdown_countdown_version": "1.6.2A",
109 | "sm_spray_version": "5.8a",
110 | "sm_stunmod_version": "1.4.4.7",
111 | "sm_taunt_version": "0.3",
112 | "sm_tidychat_version": "0.4",
113 | "sm_traderep_version": "1.0.2A",
114 | "sm_updater_version": "1.2.2",
115 | "sourcecomms_version": "0.9.266",
116 | "sourcemod_version": "1.7.3-dev+5240",
117 | "steamrep_checker_version": "1.2.0A",
118 | "steamtools_version": "0.9.0+d5d0838",
119 | "stripper_version": "1.2.2",
120 | "st_gamedesc_override_version": "1.1.3A",
121 | "sv_accelerate": "10",
122 | "sv_airaccelerate": "10",
123 | "sv_alltalk": "1",
124 | "sv_bounce": "0",
125 | "sv_cheats": "0",
126 | "sv_contact": "service@firepoweredgaming.com",
127 | "sv_footsteps": "1",
128 | "sv_friction": "4",
129 | "sv_gravity": "800",
130 | "sv_maxspeed": "320",
131 | "sv_maxusrcmdprocessticks": "24",
132 | "sv_noclipaccelerate": "5",
133 | "sv_noclipspeed": "5",
134 | "sv_password": "0",
135 | "sv_pausable": "0",
136 | "sv_registration_message": "No account specified",
137 | "sv_registration_successful": "0",
138 | "sv_rollangle": "0",
139 | "sv_rollspeed": "200",
140 | "sv_specaccelerate": "5",
141 | "sv_specnoclip": "1",
142 | "sv_specspeed": "3",
143 | "sv_steamgroup": "",
144 | "sv_stepsize": "18",
145 | "sv_stopspeed": "100",
146 | "sv_tags": "FirePowered,alltalk,backpack.tf,increased_maxplayers,no_ads,noads,nopinion,norespawntime,trade,trading,unusual",
147 | "sv_voiceenable": "1",
148 | "sv_vote_quorum_ratio": "0.6",
149 | "sv_wateraccelerate": "10",
150 | "sv_waterfriction": "1",
151 | "teamswitch_version": "1.3",
152 | "tf2items_giveweapon_version": "3.14159",
153 | "tf2items_manager": "1",
154 | "tf2items_manager_version": "1.4.1",
155 | "tf2items_version": "1.6.2",
156 | "tfh_aprilfools": "0",
157 | "tfh_birthday": "1",
158 | "tfh_enabled": "1",
159 | "tfh_endoftheline": "0",
160 | "tfh_fullmoon": "1",
161 | "tfh_halloween": "0",
162 | "tfh_normalhealth": "0",
163 | "tfh_valentines": "0",
164 | "tfh_version": "1.10.2",
165 | "tfh_winter": "1",
166 | "tf_allow_player_use": "0",
167 | "tf_arena_change_limit": "1",
168 | "tf_arena_first_blood": "1",
169 | "tf_arena_force_class": "0",
170 | "tf_arena_max_streak": "3",
171 | "tf_arena_override_cap_enable_time": "-1",
172 | "tf_arena_preround_time": "10",
173 | "tf_arena_round_time": "0",
174 | "tf_arena_use_queue": "1",
175 | "tf_beta_content": "0",
176 | "tf_birthday": "0",
177 | "tf_bot_count": "0",
178 | "tf_classlimit": "0",
179 | "tf_ctf_bonus_time": "10",
180 | "tf_damage_disablespread": "1",
181 | "tf_force_holidays_off": "0",
182 | "tf_gamemode_arena": "0",
183 | "tf_gamemode_cp": "0",
184 | "tf_gamemode_ctf": "0",
185 | "tf_gamemode_mvm": "0",
186 | "tf_gamemode_passtime": "0",
187 | "tf_gamemode_payload": "0",
188 | "tf_gamemode_pd": "0",
189 | "tf_gamemode_rd": "0",
190 | "tf_gamemode_sd": "0",
191 | "tf_max_charge_speed": "750",
192 | "tf_medieval": "0",
193 | "tf_medieval_autorp": "1",
194 | "tf_mm_servermode": "1",
195 | "tf_mm_strict": "0",
196 | "tf_mm_trusted": "0",
197 | "tf_mvm_death_penalty": "0",
198 | "tf_mvm_min_players_to_start": "3",
199 | "tf_overtime_nag": "0",
200 | "tf_passtime_ball_carrier_regen_dmgtime": "3",
201 | "tf_passtime_ball_carrier_regen_interval": "1",
202 | "tf_passtime_ball_carrier_regen_maxpct": "1",
203 | "tf_passtime_ball_carrier_regen_scale": "0.1f",
204 | "tf_passtime_ball_damping_scale": "0.01f",
205 | "tf_passtime_ball_drag_coefficient": "0.01f",
206 | "tf_passtime_ball_inertia_scale": "1.0f",
207 | "tf_passtime_ball_mass": "1.0f",
208 | "tf_passtime_ball_model": "models/passtime/ball/passtime_ball.mdl",
209 | "tf_passtime_ball_radius": "7.2f",
210 | "tf_passtime_ball_reset_time": "15",
211 | "tf_passtime_ball_rotdamping_scale": "1.0f",
212 | "tf_passtime_ball_seek_range": "128",
213 | "tf_passtime_ball_seek_speed_factor": "1.5f",
214 | "tf_passtime_ball_takedamage": "1",
215 | "tf_passtime_ball_takedamage_force": "800.0f",
216 | "tf_passtime_flinch_boost": "0",
217 | "tf_passtime_mode_homing_lock_sec": "1.5f",
218 | "tf_passtime_mode_homing_speed": "1000.0f",
219 | "tf_passtime_player_reticles_enemies": "1",
220 | "tf_passtime_player_reticles_friends": "2",
221 | "tf_passtime_score_crit_sec": "5.0f",
222 | "tf_passtime_speedboost_on_get_ball_time": "2.0f",
223 | "tf_passtime_steal_on_melee": "1",
224 | "tf_passtime_teammate_steal_time": "45",
225 | "tf_passtime_throwarc_demoman": "0.3f",
226 | "tf_passtime_throwarc_engineer": "0.3f",
227 | "tf_passtime_throwarc_heavy": "0.3f",
228 | "tf_passtime_throwarc_medic": "0.3f",
229 | "tf_passtime_throwarc_pyro": "0.3f",
230 | "tf_passtime_throwarc_scout": "0.3f",
231 | "tf_passtime_throwarc_sniper": "0.3f",
232 | "tf_passtime_throwarc_soldier": "0.3f",
233 | "tf_passtime_throwarc_spy": "0.3f",
234 | "tf_passtime_throwspeed_demoman": "1000.0f",
235 | "tf_passtime_throwspeed_engineer": "1000.0f",
236 | "tf_passtime_throwspeed_heavy": "1000.0f",
237 | "tf_passtime_throwspeed_medic": "1000.0f",
238 | "tf_passtime_throwspeed_pyro": "1000.0f",
239 | "tf_passtime_throwspeed_scout": "1000.0f",
240 | "tf_passtime_throwspeed_sniper": "1000.0f",
241 | "tf_passtime_throwspeed_soldier": "1000.0f",
242 | "tf_passtime_throwspeed_spy": "1000.0f",
243 | "tf_passtime_throwspeed_velocity_scale": "0",
244 | "tf_playergib": "1",
245 | "tf_player_name_change_time": "60",
246 | "tf_powerup_mode": "0",
247 | "tf_server_identity_disable_quickplay": "0",
248 | "tf_spec_xray": "1",
249 | "tf_spells_enabled": "0",
250 | "tf_teamtalk": "1",
251 | "tf_use_fixed_weaponspreads": "0",
252 | "tf_weapon_criticals": "1",
253 | "tf_weapon_criticals_melee": "1",
254 | "thirdperson_version": "2.1.0",
255 | "tidykick_version": "1.1.5A",
256 | "tv_enable": "1",
257 | "tv_password": "1",
258 | "tv_relaypassword": "0",
259 | "uberpunisher_version": "1.5.2",
260 | "ufov_version": "1.2.0A",
261 | "voiceannounce_ex_version": "2.0.0",
262 | "votekick_switcher_version": "1.3.0A"
263 | }
264 |
--------------------------------------------------------------------------------
/Tests/Rules/tf2_sourcemod.raw:
--------------------------------------------------------------------------------
1 | feffffff570100000600e004ffffffff450501616e74695f6632705f76657273696f6e00322e312e30006261636b7061636b5f74665f76657273696f6e00322e31312e3141006261636b7061636b5f7669657765725f76657273696f6e00312e312e3241006265746865686f7273656d616e6e5f76657273696f6e00312e3100636f6e6e6563745f6d6574686f645f76657273696f6e00312e322e304100636f6e6e6563745f76657273696f6e00312e322e3000636f6f70003000637573746f6d5f636861745f636f6c6f72735f6d7973716c5f76657273696f6e00312e312e334100637573746f6d5f636861745f636f6c6f72735f746f67676c655f76657273696f6e00322e302e304100637573746f6d5f636861745f636f6c6f72735f76657273696f6e00332e312e30410064656174686d61746368003100646563616c6672657175656e63790031300064796e616d69636d6f74645f76657273696f6e00322e322e337300656e68616e6365645f6974656d735f76657273696f6e00312e312e30410066616c73656d657373616765735f76657273696f6e00332e3100676f746f5f76657273696f6e00312e3200696d706572736f6e6174655f76657273696f6e00312e352e30006b6172746966795f76657273696f6e00312e352e3041006c6f63616c5f6974656d5f7365727665725f76657273696f6e00312e312e3541006d6574616d6f645f76657273696f6e00312e31302e362d64657656006d705f616c6c6f774e5043730031006d705f6175746f63726f7373686169720031006d705f6175746f7465616d62616c616e63650030006d705f64697361626c655f7265737061776e5f74696d65730031006d705f66616465746f626c61636b0030006d705f66616c6c64616d6167650030006d705f666c6173686c696768740030006d705f666f6f7473746570730031006d705f666f7263656175746f7465616d0030006d705f666f7263657265737061776e0031006d705f667261676c696d69740030006d705f667269656e646c79666972650030006d705f686967686c616e6465720030006d705f686f6c696461795f6e6f67696674730030006d705f6d617463685f656e645f61745f74696d656c696d69740031006d705f6d6178726f756e64730030006d705f7265737061776e7761766574696d650031302e30006d705f736372616d626c657465616d735f6175746f0030006d705f736372616d626c657465616d735f6175746f5f77696e646966666572656e63650033006d705f7374616c656d6174655f656e61626c650030006d705f7374616c656d6174655f6d656c65656f6e6c790030006d705f7465616d6c69737400686772756e743b736369656e74697374006d705f7465616d706c61790030006d705f74696d656c696d697400333030006d705f746f75726e616d656e740030006d705f746f75726e616d656e745f72656164796d6f64650030006d705f746f75726e616d656e745f72656164796d6f64655f636f756e74646f776e003130006d705f746f75726e616d656e745f72656164796d6f64655f6d696e0032006d705f746f75726e616d656e745f72656164796d6f64655f7465616d5f73697a650030006d705f746f75726e616d656e745f73746f7077617463680031006d705f776561706f6e737461790030006d705f77696e646966666572656e63650030006d705f77696e646966666572656e63655f6d696e0030
2 | feffffff570100000601e004006d705f77696e6c696d69740030006e6578746c6576656c00006e6f5f656e656d795f696e5f737061776e5f76657273696f6e00312e322e314100706c617965725f616e616c79746963735f76657273696f6e00312e332e3141007261696e626f77697a655f76657273696f6e00312e372e30410072706c617965725f76657273696f6e00312e3100725f416972626f61745669657744616d70656e44616d7000312e3000725f416972626f61745669657744616d70656e4672657100372e3000725f416972626f6174566965775a48656967687400302e3000725f4a6565705669657744616d70656e44616d7000312e3000725f4a6565705669657744616d70656e4672657100372e3000725f4a656570566965775a4865696768740031302e3000725f56656869636c655669657744616d70656e0031007362636865636b65725f76657273696f6e00312e302e320073625f76657273696f6e00312e342e3131007363705f76657273696f6e00322e312e304100736574756265725f76657273696f6e00312e3300736d646a5f76657273696f6e00322e362e3100736d5f61646d696e736d6974655f76657273696f6e00322e3100736d5f61696d6e616d65735f76657273696f6e00302e3800736d5f616c5f76657273696f6e00312e3000736d5f616d6d6f7061636b737061776e65725f76657273696f6e00312e302e3000736d5f616e7469636f6c6f7261627573655f76657273696f6e00312e302e3000736d5f62676f645f76657273696f6e00312e302e3100736d5f6268735f76657273696f6e00312e302e3000736d5f626c6565645f76657273696f6e00312e302e3000736d5f64657374726f795f76657273696f6e00312e322e3000736d5f646973636f5f76657273696f6e00302e332e3000736d5f66616b6567696674735f76657273696f6e00312e302e3100736d5f66616b656974656d5f76657273696f6e00312e332e3000736d5f66637661725f76657273696f6e00312e3100736d5f6669615f76657273696f6e00322e322e3400736d5f66756e636f6d6d616e6473785f76657273696f6e00322e3200736d5f676f646d6f64655f76657273696f6e00322e332e3100736d5f6865616c74687061636b5f737061776e65725f76657273696f6e00312e302e3000736d5f686f7273656d616e6e5f76657273696f6e00312e3100736d5f6d657261736d75735f76657273696f6e00312e342e3200736d5f6d6f6e6f737061776e5f76657273696f6e00312e312e3100736d5f6d757465636865636b5f76657273696f6e00312e392e324100736d5f6e6578746d61700074726164655f756e757375616c5f63656e7465725f763300736d5f6e6f6973656d616b65725f76657273696f6e00322e322e3000736d5f706f776572706c61795f76657273696f6e00312e352e336d00736d5f726166666c655f76657273696f6e00302e3900736d5f726567656e5f76657273696f6e00312e3000736d5f726573697a655f76657273696f6e00312e322e3000736d5f72776561706f6e735f76657273696f6e00312e3300736d5f736574616d6d6f5f76657273696f6e00312e312e3000736d5f736574636c6173735f63686174003100736d5f736574636c6173735f6c6f67003100736d5f736574636c6173735f76657273696f6e00312e322e3000736d5f73657473706565645f63686174003100736d5f736574737065
3 | feffffff570100000602e00465645f6c6f67003100736d5f73657473706565645f76657273696f6e00312e332e3100736d5f73687574646f776e5f636f756e74646f776e5f76657273696f6e00312e362e324100736d5f73707261795f76657273696f6e00352e386100736d5f7374756e6d6f645f76657273696f6e00312e342e342e3700736d5f7461756e745f76657273696f6e00302e3300736d5f74696479636861745f76657273696f6e00302e3400736d5f74726164657265705f76657273696f6e00312e302e324100736d5f757064617465725f76657273696f6e00312e322e3200736f75726365636f6d6d735f76657273696f6e00302e392e32363600736f757263656d6f645f76657273696f6e00312e372e332d6465762b3532343000737465616d7265705f636865636b65725f76657273696f6e00312e322e304100737465616d746f6f6c735f76657273696f6e00302e392e302b643564303833380073747269707065725f76657273696f6e00312e322e320073745f67616d65646573635f6f766572726964655f76657273696f6e00312e312e33410073765f616363656c65726174650031300073765f616972616363656c65726174650031300073765f616c6c74616c6b00310073765f626f756e636500300073765f63686561747300300073765f636f6e7461637400736572766963654066697265706f776572656467616d696e672e636f6d0073765f666f6f74737465707300310073765f6672696374696f6e00340073765f67726176697479003830300073765f6d61787370656564003332300073765f6d6178757372636d6470726f636573737469636b730032340073765f6e6f636c6970616363656c657261746500350073765f6e6f636c6970737065656400350073765f70617373776f726400300073765f7061757361626c6500300073765f726567697374726174696f6e5f6d657373616765004e6f206163636f756e74207370656369666965640073765f726567697374726174696f6e5f7375636365737366756c00300073765f726f6c6c616e676c6500300073765f726f6c6c7370656564003230300073765f73706563616363656c657261746500350073765f737065636e6f636c697000310073765f73706563737065656400330073765f737465616d67726f7570000073765f7374657073697a650031380073765f73746f707370656564003130300073765f746167730046697265506f77657265642c616c6c74616c6b2c6261636b7061636b2e74662c696e637265617365645f6d6178706c61796572732c6e6f5f6164732c6e6f6164732c6e6f70696e696f6e2c6e6f7265737061776e74696d652c74726164652c74726164696e672c756e757375616c0073765f766f696365656e61626c6500310073765f766f74655f71756f72756d5f726174696f00302e360073765f7761746572616363656c65726174650031300073765f77617465726672696374696f6e0031007465616d7377697463685f76657273696f6e00312e33007466326974656d735f67697665776561706f6e5f76657273696f6e00332e3134313539007466326974656d735f6d616e616765720031007466326974656d735f6d616e616765725f76657273696f6e00312e342e31007466326974656d735f76657273696f6e00312e362e32007466685f617072696c666f6f6c730030007466685f62697274686461790031007466685f65
4 | feffffff570100000603e0046e61626c65640031007466685f656e646f667468656c696e650030007466685f66756c6c6d6f6f6e0031007466685f68616c6c6f7765656e0030007466685f6e6f726d616c6865616c74680030007466685f76616c656e74696e65730030007466685f76657273696f6e00312e31302e32007466685f77696e74657200310074665f616c6c6f775f706c617965725f75736500300074665f6172656e615f6368616e67655f6c696d697400310074665f6172656e615f66697273745f626c6f6f6400310074665f6172656e615f666f7263655f636c61737300300074665f6172656e615f6d61785f73747265616b00330074665f6172656e615f6f766572726964655f6361705f656e61626c655f74696d65002d310074665f6172656e615f707265726f756e645f74696d650031300074665f6172656e615f726f756e645f74696d6500300074665f6172656e615f7573655f717565756500310074665f626574615f636f6e74656e7400300074665f626972746864617900300074665f626f745f636f756e7400300074665f636c6173736c696d697400300074665f6374665f626f6e75735f74696d650031300074665f64616d6167655f64697361626c6573707265616400310074665f666f7263655f686f6c69646179735f6f666600300074665f67616d656d6f64655f6172656e6100300074665f67616d656d6f64655f637000300074665f67616d656d6f64655f63746600300074665f67616d656d6f64655f6d766d00300074665f67616d656d6f64655f7061737374696d6500300074665f67616d656d6f64655f7061796c6f616400300074665f67616d656d6f64655f706400300074665f67616d656d6f64655f726400300074665f67616d656d6f64655f736400300074665f6d61785f6368617267655f7370656564003735300074665f6d6564696576616c00300074665f6d6564696576616c5f6175746f727000310074665f6d6d5f7365727665726d6f646500310074665f6d6d5f73747269637400300074665f6d6d5f7472757374656400300074665f6d766d5f64656174685f70656e616c747900300074665f6d766d5f6d696e5f706c61796572735f746f5f737461727400330074665f6f76657274696d655f6e616700300074665f7061737374696d655f62616c6c5f636172726965725f726567656e5f646d6774696d6500330074665f7061737374696d655f62616c6c5f636172726965725f726567656e5f696e74657276616c00310074665f7061737374696d655f62616c6c5f636172726965725f726567656e5f6d617870637400310074665f7061737374696d655f62616c6c5f636172726965725f726567656e5f7363616c6500302e31660074665f7061737374696d655f62616c6c5f64616d70696e675f7363616c6500302e3031660074665f7061737374696d655f62616c6c5f647261675f636f656666696369656e7400302e3031660074665f7061737374696d655f62616c6c5f696e65727469615f7363616c6500312e30660074665f7061737374696d655f62616c6c5f6d61737300312e30660074665f7061737374696d655f62616c6c5f6d6f64656c006d6f64656c732f7061737374696d652f62616c6c2f7061737374696d655f62616c6c2e6d646c0074665f7061737374696d655f62616c6c5f72616469757300372e32660074665f7061737374696d655f62616c6c5f7265736574
5 | feffffff570100000604e0045f74696d650031350074665f7061737374696d655f62616c6c5f726f7464616d70696e675f7363616c6500312e30660074665f7061737374696d655f62616c6c5f7365656b5f72616e6765003132380074665f7061737374696d655f62616c6c5f7365656b5f73706565645f666163746f7200312e35660074665f7061737374696d655f62616c6c5f74616b6564616d61676500310074665f7061737374696d655f62616c6c5f74616b6564616d6167655f666f726365003830302e30660074665f7061737374696d655f666c696e63685f626f6f737400300074665f7061737374696d655f6d6f64655f686f6d696e675f6c6f636b5f73656300312e35660074665f7061737374696d655f6d6f64655f686f6d696e675f737065656400313030302e30660074665f7061737374696d655f706c617965725f72657469636c65735f656e656d69657300310074665f7061737374696d655f706c617965725f72657469636c65735f667269656e647300320074665f7061737374696d655f73636f72655f637269745f73656300352e30660074665f7061737374696d655f7370656564626f6f73745f6f6e5f6765745f62616c6c5f74696d6500322e30660074665f7061737374696d655f737465616c5f6f6e5f6d656c656500310074665f7061737374696d655f7465616d6d6174655f737465616c5f74696d650034350074665f7061737374696d655f7468726f776172635f64656d6f6d616e00302e33660074665f7061737374696d655f7468726f776172635f656e67696e65657200302e33660074665f7061737374696d655f7468726f776172635f686561767900302e33660074665f7061737374696d655f7468726f776172635f6d6564696300302e33660074665f7061737374696d655f7468726f776172635f7079726f00302e33660074665f7061737374696d655f7468726f776172635f73636f757400302e33660074665f7061737374696d655f7468726f776172635f736e6970657200302e33660074665f7061737374696d655f7468726f776172635f736f6c6469657200302e33660074665f7061737374696d655f7468726f776172635f73707900302e33660074665f7061737374696d655f7468726f7773706565645f64656d6f6d616e00313030302e30660074665f7061737374696d655f7468726f7773706565645f656e67696e65657200313030302e30660074665f7061737374696d655f7468726f7773706565645f686561767900313030302e30660074665f7061737374696d655f7468726f7773706565645f6d6564696300313030302e30660074665f7061737374696d655f7468726f7773706565645f7079726f00313030302e30660074665f7061737374696d655f7468726f7773706565645f73636f757400313030302e30660074665f7061737374696d655f7468726f7773706565645f736e6970657200313030302e30660074665f7061737374696d655f7468726f7773706565645f736f6c6469657200313030302e30660074665f7061737374696d655f7468726f7773706565645f73707900313030302e30660074665f7061737374696d655f7468726f7773706565645f76656c6f636974795f7363616c6500300074665f706c6179657267696200310074665f706c617965725f6e616d655f6368616e67655f74696d650036300074665f706f77657275705f6d6f646500300074665f
6 | feffffff570100000605e0047365727665725f6964656e746974795f64697361626c655f717569636b706c617900300074665f737065635f7872617900310074665f7370656c6c735f656e61626c656400300074665f7465616d74616c6b00310074665f7573655f66697865645f776561706f6e7370726561647300300074665f776561706f6e5f637269746963616c7300310074665f776561706f6e5f637269746963616c735f6d656c65650031007468697264706572736f6e5f76657273696f6e00322e312e3000746964796b69636b5f76657273696f6e00312e312e35410074765f656e61626c6500310074765f70617373776f726400310074765f72656c617970617373776f72640030007562657270756e69736865725f76657273696f6e00312e352e320075666f765f76657273696f6e00312e322e304100766f696365616e6e6f756e63655f65785f76657273696f6e00322e302e3000766f74656b69636b5f73776974636865725f76657273696f6e00312e332e304100
7 |
--------------------------------------------------------------------------------
/SourceQuery/SourceQuery.php:
--------------------------------------------------------------------------------
1 | Socket = $Socket ?? new Socket( );
94 | }
95 |
96 | public function __destruct( )
97 | {
98 | $this->Disconnect( );
99 | }
100 |
101 | /**
102 | * Opens connection to server
103 | *
104 | * @param string $Address Server ip
105 | * @param int $Port Server port
106 | * @param int $Timeout Timeout period
107 | * @param int $Engine Engine the server runs on (goldsource, source)
108 | *
109 | * @throws InvalidArgumentException
110 | * @throws SocketException
111 | */
112 | public function Connect( string $Address, int $Port, int $Timeout = 3, int $Engine = self::SOURCE ) : void
113 | {
114 | $this->Disconnect( );
115 |
116 | if( $Timeout < 0 )
117 | {
118 | throw new InvalidArgumentException( 'Timeout must be a positive integer.', InvalidArgumentException::TIMEOUT_NOT_INTEGER );
119 | }
120 |
121 | $this->Socket->Open( $Address, $Port, $Timeout, $Engine );
122 |
123 | $this->Connected = true;
124 | }
125 |
126 | /**
127 | * Forces GetChallenge to use old method for challenge retrieval because some games use outdated protocol (e.g Starbound)
128 | *
129 | * @param bool $Value Set to true to force old method
130 | *
131 | * @return bool Previous value
132 | */
133 | public function SetUseOldGetChallengeMethod( bool $Value ) : bool
134 | {
135 | $Previous = $this->UseOldGetChallengeMethod;
136 |
137 | $this->UseOldGetChallengeMethod = $Value === true;
138 |
139 | return $Previous;
140 | }
141 |
142 | /**
143 | * Closes all open connections
144 | */
145 | public function Disconnect( ) : void
146 | {
147 | $this->Connected = false;
148 | $this->Challenge = '';
149 |
150 | $this->Socket->Close( );
151 |
152 | if( $this->Rcon !== null )
153 | {
154 | $this->Rcon->Close( );
155 |
156 | $this->Rcon = null;
157 | }
158 | }
159 |
160 | /**
161 | * Sends ping packet to the server
162 | * NOTE: This may not work on some games (TF2 for example)
163 | *
164 | * @throws InvalidPacketException
165 | * @throws SocketException
166 | *
167 | * @return bool True on success, false on failure
168 | */
169 | public function Ping( ) : bool
170 | {
171 | if( !$this->Connected )
172 | {
173 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
174 | }
175 |
176 | $this->Socket->Write( self::A2A_PING );
177 | $Buffer = $this->Socket->Read( );
178 |
179 | return $Buffer->ReadByte( ) === self::A2A_ACK;
180 | }
181 |
182 | /**
183 | * Get server information
184 | *
185 | * @throws InvalidPacketException
186 | * @throws SocketException
187 | *
188 | * @return array{
189 | * Protocol: int,
190 | * HostName: string,
191 | * Map: string,
192 | * ModDir: string,
193 | * ModDesc: string,
194 | * AppID?: int,
195 | * Players: int,
196 | * MaxPlayers: int,
197 | * Bots: int,
198 | * Dedicated: string,
199 | * Os: string,
200 | * Password: bool,
201 | * Secure: bool,
202 | * Version?: string,
203 | * ExtraDataFlags?: int,
204 | * GamePort?: int,
205 | * SteamID?: string|int,
206 | * SpecPort?: int,
207 | * SpecName?: string,
208 | * GameTags?: string,
209 | * GameID?: int,
210 | * Address?: string,
211 | * IsMod?: bool,
212 | * Mod?: array{
213 | * Url: string,
214 | * Download: string,
215 | * Version: int,
216 | * Size: int,
217 | * ServerSide: bool,
218 | * CustomDLL: bool
219 | * },
220 | * GameMode?: int,
221 | * WitnessCount?: int,
222 | * WitnessTime?: int
223 | * } Returns an array with server information on success
224 | */
225 | public function GetInfo( ) : array
226 | {
227 | if( !$this->Connected )
228 | {
229 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
230 | }
231 |
232 | if( $this->Challenge )
233 | {
234 | $this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" . $this->Challenge );
235 | }
236 | else
237 | {
238 | $this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" );
239 | }
240 |
241 | $Buffer = $this->Socket->Read( );
242 | $Type = $Buffer->ReadByte( );
243 | $Server = [];
244 |
245 | if( $Type === self::S2C_CHALLENGE )
246 | {
247 | $this->Challenge = $Buffer->Read( 4 );
248 |
249 | $this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" . $this->Challenge );
250 | $Buffer = $this->Socket->Read( );
251 | $Type = $Buffer->ReadByte( );
252 | }
253 |
254 | // Old GoldSource protocol, HLTV still uses it
255 | if( $Type === self::S2A_INFO_OLD && $this->Socket->Engine === self::GOLDSOURCE )
256 | {
257 | /**
258 | * If we try to read data again, and we get the result with type S2A_INFO (0x49)
259 | * That means this server is running dproto,
260 | * Because it sends answer for both protocols
261 | */
262 |
263 | $Server[ 'Address' ] = $Buffer->ReadNullTermString( );
264 | $Server[ 'HostName' ] = $Buffer->ReadNullTermString( );
265 | $Server[ 'Map' ] = $Buffer->ReadNullTermString( );
266 | $Server[ 'ModDir' ] = $Buffer->ReadNullTermString( );
267 | $Server[ 'ModDesc' ] = $Buffer->ReadNullTermString( );
268 | $Server[ 'Players' ] = $Buffer->ReadByte( );
269 | $Server[ 'MaxPlayers' ] = $Buffer->ReadByte( );
270 | $Server[ 'Protocol' ] = $Buffer->ReadByte( );
271 | $Server[ 'Dedicated' ] = chr( $Buffer->ReadByte( ) );
272 | $Server[ 'Os' ] = chr( $Buffer->ReadByte( ) );
273 | $Server[ 'Password' ] = $Buffer->ReadByte( ) === 1;
274 | $Server[ 'IsMod' ] = $Buffer->ReadByte( ) === 1;
275 |
276 | if( $Server[ 'IsMod' ] )
277 | {
278 | $Mod = [];
279 | $Mod[ 'Url' ] = $Buffer->ReadNullTermString( );
280 | $Mod[ 'Download' ] = $Buffer->ReadNullTermString( );
281 | $Buffer->Read( 1 ); // NULL byte
282 | $Mod[ 'Version' ] = $Buffer->ReadInt32( );
283 | $Mod[ 'Size' ] = $Buffer->ReadInt32( );
284 | $Mod[ 'ServerSide' ] = $Buffer->ReadByte( ) === 1;
285 | $Mod[ 'CustomDLL' ] = $Buffer->ReadByte( ) === 1;
286 | $Server[ 'Mod' ] = $Mod;
287 | }
288 |
289 | $Server[ 'Secure' ] = $Buffer->ReadByte( ) === 1;
290 | $Server[ 'Bots' ] = $Buffer->ReadByte( );
291 |
292 | return $Server;
293 | }
294 |
295 | if( $Type !== self::S2A_INFO_SRC )
296 | {
297 | throw new InvalidPacketException( 'GetInfo: Packet header mismatch. (0x' . dechex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
298 | }
299 |
300 | $Server[ 'Protocol' ] = $Buffer->ReadByte( );
301 | $Server[ 'HostName' ] = $Buffer->ReadNullTermString( );
302 | $Server[ 'Map' ] = $Buffer->ReadNullTermString( );
303 | $Server[ 'ModDir' ] = $Buffer->ReadNullTermString( );
304 | $Server[ 'ModDesc' ] = $Buffer->ReadNullTermString( );
305 | $Server[ 'AppID' ] = $Buffer->ReadInt16( );
306 | $Server[ 'Players' ] = $Buffer->ReadByte( );
307 | $Server[ 'MaxPlayers' ] = $Buffer->ReadByte( );
308 | $Server[ 'Bots' ] = $Buffer->ReadByte( );
309 | $Server[ 'Dedicated' ] = chr( $Buffer->ReadByte( ) );
310 | $Server[ 'Os' ] = chr( $Buffer->ReadByte( ) );
311 | $Server[ 'Password' ] = $Buffer->ReadByte( ) === 1;
312 | $Server[ 'Secure' ] = $Buffer->ReadByte( ) === 1;
313 |
314 | // The Ship (they violate query protocol spec by modifying the response)
315 | if( $Server[ 'AppID' ] === 2400 )
316 | {
317 | $Server[ 'GameMode' ] = $Buffer->ReadByte( );
318 | $Server[ 'WitnessCount' ] = $Buffer->ReadByte( );
319 | $Server[ 'WitnessTime' ] = $Buffer->ReadByte( );
320 | }
321 |
322 | $Server[ 'Version' ] = $Buffer->ReadNullTermString( );
323 |
324 | // Extra Data Flags
325 | if( $Buffer->Remaining( ) > 0 )
326 | {
327 | $Server[ 'ExtraDataFlags' ] = $Flags = $Buffer->ReadByte( );
328 |
329 | // S2A_EXTRA_DATA_HAS_GAME_PORT - Next 2 bytes include the game port.
330 | if( $Flags & 0x80 )
331 | {
332 | $Server[ 'GamePort' ] = $Buffer->ReadInt16( );
333 | }
334 |
335 | // S2A_EXTRA_DATA_HAS_STEAMID - Next 8 bytes are the steamID
336 | // Want to play around with this?
337 | // You can use https://github.com/xPaw/SteamID.php
338 | if( $Flags & 0x10 )
339 | {
340 | $SteamIDLower = $Buffer->ReadUInt32( );
341 | $SteamIDInstance = $Buffer->ReadUInt32( ); // This gets shifted by 32 bits, which should be steamid instance
342 | $SteamID = 0;
343 |
344 | if( PHP_INT_SIZE === 4 )
345 | {
346 | if( extension_loaded( 'gmp' ) )
347 | {
348 | $SteamIDLower = gmp_abs( $SteamIDLower );
349 | $SteamIDInstance = gmp_abs( $SteamIDInstance );
350 | $SteamID = gmp_strval( gmp_or( $SteamIDLower, gmp_mul( $SteamIDInstance, gmp_pow( 2, 32 ) ) ) );
351 | }
352 | else
353 | {
354 | throw new \RuntimeException( 'Either 64-bit PHP installation or "gmp" module is required to correctly parse server\'s steamid.' );
355 | }
356 | }
357 | else
358 | {
359 | $SteamID = $SteamIDLower | ( $SteamIDInstance << 32 );
360 | }
361 |
362 | $Server[ 'SteamID' ] = $SteamID;
363 |
364 | unset( $SteamIDLower, $SteamIDInstance, $SteamID );
365 | }
366 |
367 | // S2A_EXTRA_DATA_HAS_SPECTATOR_DATA - Next 2 bytes include the spectator port, then the spectator server name.
368 | if( $Flags & 0x40 )
369 | {
370 | $Server[ 'SpecPort' ] = $Buffer->ReadInt16( );
371 | $Server[ 'SpecName' ] = $Buffer->ReadNullTermString( );
372 | }
373 |
374 | // S2A_EXTRA_DATA_HAS_GAMETAG_DATA - Next bytes are the game tag string
375 | if( $Flags & 0x20 )
376 | {
377 | $Server[ 'GameTags' ] = $Buffer->ReadNullTermString( );
378 | }
379 |
380 | // S2A_EXTRA_DATA_GAMEID - Next 8 bytes are the gameID of the server
381 | if( $Flags & 0x01 )
382 | {
383 | $Server[ 'GameID' ] = $Buffer->ReadUInt32( ) | ( $Buffer->ReadUInt32( ) << 32 );
384 | }
385 |
386 | if( $Buffer->Remaining( ) > 0 )
387 | {
388 | throw new InvalidPacketException( 'GetInfo: unread data? ' . $Buffer->Remaining( ) . ' bytes remaining in the buffer. Please report it to the library developer.',
389 | InvalidPacketException::BUFFER_NOT_EMPTY );
390 | }
391 | }
392 |
393 | return $Server;
394 | }
395 |
396 | /**
397 | * Get players on the server
398 | *
399 | * @throws InvalidPacketException
400 | * @throws SocketException
401 | *
402 | * @return array Returns an array with players on success
403 | */
404 | public function GetPlayers( ) : array
405 | {
406 | if( !$this->Connected )
407 | {
408 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
409 | }
410 |
411 | $this->GetChallenge( self::A2S_PLAYER, self::S2A_PLAYER );
412 |
413 | $this->Socket->Write( self::A2S_PLAYER, $this->Challenge );
414 | $Buffer = $this->Socket->Read( );
415 |
416 | $Type = $Buffer->ReadByte( );
417 |
418 | if( $Type !== self::S2A_PLAYER )
419 | {
420 | throw new InvalidPacketException( 'GetPlayers: Packet header mismatch. (0x' . dechex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
421 | }
422 |
423 | $Players = [];
424 | $Count = $Buffer->ReadByte( );
425 |
426 | while( $Count-- > 0 && $Buffer->Remaining( ) > 0 )
427 | {
428 | $Player = [];
429 | $Player[ 'Id' ] = $Buffer->ReadByte( ); // PlayerID, is it just always 0?
430 | $Player[ 'Name' ] = $Buffer->ReadNullTermString( );
431 | $Player[ 'Frags' ] = $Buffer->ReadInt32( );
432 | $Player[ 'Time' ] = (int)$Buffer->ReadFloat32( );
433 | $Player[ 'TimeF' ] = gmdate( ( $Player[ 'Time' ] > 3600 ? 'H:i:s' : 'i:s' ), $Player[ 'Time' ] );
434 |
435 | $Players[ ] = $Player;
436 | }
437 |
438 | return $Players;
439 | }
440 |
441 | /**
442 | * Get rules (cvars) from the server
443 | *
444 | * @throws InvalidPacketException
445 | * @throws SocketException
446 | *
447 | * @return array Returns an array with rules on success
448 | */
449 | public function GetRules( ) : array
450 | {
451 | if( !$this->Connected )
452 | {
453 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
454 | }
455 |
456 | $this->GetChallenge( self::A2S_RULES, self::S2A_RULES );
457 |
458 | $this->Socket->Write( self::A2S_RULES, $this->Challenge );
459 | $Buffer = $this->Socket->Read( );
460 |
461 | $Type = $Buffer->ReadByte( );
462 |
463 | if( $Type !== self::S2A_RULES )
464 | {
465 | throw new InvalidPacketException( 'GetRules: Packet header mismatch. (0x' . dechex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
466 | }
467 |
468 | $Rules = [];
469 | $Count = $Buffer->ReadInt16( );
470 |
471 | while( $Count-- > 0 && $Buffer->Remaining( ) > 0 )
472 | {
473 | $Rule = $Buffer->ReadNullTermString( );
474 | $Value = $Buffer->ReadNullTermString( );
475 |
476 | if( strlen( $Rule ) > 0 )
477 | {
478 | $Rules[ $Rule ] = $Value;
479 | }
480 | }
481 |
482 | return $Rules;
483 | }
484 |
485 | /**
486 | * Get challenge (used for players/rules packets)
487 | *
488 | * @throws InvalidPacketException
489 | */
490 | private function GetChallenge( int $Header, int $ExpectedResult ) : void
491 | {
492 | if( $this->Challenge )
493 | {
494 | return;
495 | }
496 |
497 | if( $this->UseOldGetChallengeMethod )
498 | {
499 | $Header = self::A2S_SERVERQUERY_GETCHALLENGE;
500 | }
501 |
502 | $this->Socket->Write( $Header, "\xFF\xFF\xFF\xFF" );
503 | $Buffer = $this->Socket->Read( );
504 |
505 | $Type = $Buffer->ReadByte( );
506 |
507 | switch( $Type )
508 | {
509 | case self::S2C_CHALLENGE:
510 | {
511 | $this->Challenge = $Buffer->Read( 4 );
512 |
513 | return;
514 | }
515 | case $ExpectedResult:
516 | {
517 | // Goldsource (HLTV)
518 |
519 | return;
520 | }
521 | case 0:
522 | {
523 | throw new InvalidPacketException( 'GetChallenge: Failed to get challenge.' );
524 | }
525 | default:
526 | {
527 | throw new InvalidPacketException( 'GetChallenge: Packet header mismatch. (0x' . dechex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
528 | }
529 | }
530 | }
531 |
532 | /**
533 | * Sets rcon password, for future use in Rcon()
534 | *
535 | * @param string $Password Rcon Password
536 | *
537 | * @throws AuthenticationException
538 | * @throws InvalidPacketException
539 | * @throws SocketException
540 | */
541 | public function SetRconPassword( string $Password ) : void
542 | {
543 | if( !$this->Connected )
544 | {
545 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
546 | }
547 |
548 | switch( $this->Socket->Engine )
549 | {
550 | case SourceQuery::GOLDSOURCE:
551 | {
552 | $this->Rcon = new GoldSourceRcon( $this->Socket );
553 |
554 | break;
555 | }
556 | case SourceQuery::SOURCE:
557 | {
558 | $this->Rcon = new SourceRcon( $this->Socket );
559 |
560 | break;
561 | }
562 | default:
563 | {
564 | throw new SocketException( 'Unknown engine.', SocketException::INVALID_ENGINE );
565 | }
566 | }
567 |
568 | if( $this->Rcon === null ) // This should not happen, but makes phpstan happy.
569 | {
570 | throw new SocketException( 'Something went wrong.', SocketException::INVALID_ENGINE );
571 | }
572 |
573 | $this->Rcon->Open( );
574 | $this->Rcon->Authorize( $Password );
575 | }
576 |
577 | /**
578 | * Sends a command to the server for execution.
579 | *
580 | * @param string $Command Command to execute
581 | *
582 | * @throws AuthenticationException
583 | * @throws InvalidPacketException
584 | * @throws SocketException
585 | *
586 | * @return string Answer from server in string
587 | */
588 | public function Rcon( string $Command ) : string
589 | {
590 | if( !$this->Connected )
591 | {
592 | throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
593 | }
594 |
595 | if( $this->Rcon === null )
596 | {
597 | throw new SocketException( 'You must set a RCON password before trying to execute a RCON command.', SocketException::NOT_CONNECTED );
598 | }
599 |
600 | return $this->Rcon->Command( $Command );
601 | }
602 | }
603 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 2.1, February 1999
3 |
4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc.
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | (This is the first released version of the Lesser GPL. It also counts
10 | as the successor of the GNU Library Public License, version 2, hence
11 | the version number 2.1.)
12 |
13 | Preamble
14 |
15 | The licenses for most software are designed to take away your
16 | freedom to share and change it. By contrast, the GNU General Public
17 | Licenses are intended to guarantee your freedom to share and change
18 | free software--to make sure the software is free for all its users.
19 |
20 | This license, the Lesser General Public License, applies to some
21 | specially designated software packages--typically libraries--of the
22 | Free Software Foundation and other authors who decide to use it. You
23 | can use it too, but we suggest you first think carefully about whether
24 | this license or the ordinary General Public License is the better
25 | strategy to use in any particular case, based on the explanations below.
26 |
27 | When we speak of free software, we are referring to freedom of use,
28 | not price. Our General Public Licenses are designed to make sure that
29 | you have the freedom to distribute copies of free software (and charge
30 | for this service if you wish); that you receive source code or can get
31 | it if you want it; that you can change the software and use pieces of
32 | it in new free programs; and that you are informed that you can do
33 | these things.
34 |
35 | To protect your rights, we need to make restrictions that forbid
36 | distributors to deny you these rights or to ask you to surrender these
37 | rights. These restrictions translate to certain responsibilities for
38 | you if you distribute copies of the library or if you modify it.
39 |
40 | For example, if you distribute copies of the library, whether gratis
41 | or for a fee, you must give the recipients all the rights that we gave
42 | you. You must make sure that they, too, receive or can get the source
43 | code. If you link other code with the library, you must provide
44 | complete object files to the recipients, so that they can relink them
45 | with the library after making changes to the library and recompiling
46 | it. And you must show them these terms so they know their rights.
47 |
48 | We protect your rights with a two-step method: (1) we copyright the
49 | library, and (2) we offer you this license, which gives you legal
50 | permission to copy, distribute and/or modify the library.
51 |
52 | To protect each distributor, we want to make it very clear that
53 | there is no warranty for the free library. Also, if the library is
54 | modified by someone else and passed on, the recipients should know
55 | that what they have is not the original version, so that the original
56 | author's reputation will not be affected by problems that might be
57 | introduced by others.
58 |
59 | Finally, software patents pose a constant threat to the existence of
60 | any free program. We wish to make sure that a company cannot
61 | effectively restrict the users of a free program by obtaining a
62 | restrictive license from a patent holder. Therefore, we insist that
63 | any patent license obtained for a version of the library must be
64 | consistent with the full freedom of use specified in this license.
65 |
66 | Most GNU software, including some libraries, is covered by the
67 | ordinary GNU General Public License. This license, the GNU Lesser
68 | General Public License, applies to certain designated libraries, and
69 | is quite different from the ordinary General Public License. We use
70 | this license for certain libraries in order to permit linking those
71 | libraries into non-free programs.
72 |
73 | When a program is linked with a library, whether statically or using
74 | a shared library, the combination of the two is legally speaking a
75 | combined work, a derivative of the original library. The ordinary
76 | General Public License therefore permits such linking only if the
77 | entire combination fits its criteria of freedom. The Lesser General
78 | Public License permits more lax criteria for linking other code with
79 | the library.
80 |
81 | We call this license the "Lesser" General Public License because it
82 | does Less to protect the user's freedom than the ordinary General
83 | Public License. It also provides other free software developers Less
84 | of an advantage over competing non-free programs. These disadvantages
85 | are the reason we use the ordinary General Public License for many
86 | libraries. However, the Lesser license provides advantages in certain
87 | special circumstances.
88 |
89 | For example, on rare occasions, there may be a special need to
90 | encourage the widest possible use of a certain library, so that it becomes
91 | a de-facto standard. To achieve this, non-free programs must be
92 | allowed to use the library. A more frequent case is that a free
93 | library does the same job as widely used non-free libraries. In this
94 | case, there is little to gain by limiting the free library to free
95 | software only, so we use the Lesser General Public License.
96 |
97 | In other cases, permission to use a particular library in non-free
98 | programs enables a greater number of people to use a large body of
99 | free software. For example, permission to use the GNU C Library in
100 | non-free programs enables many more people to use the whole GNU
101 | operating system, as well as its variant, the GNU/Linux operating
102 | system.
103 |
104 | Although the Lesser General Public License is Less protective of the
105 | users' freedom, it does ensure that the user of a program that is
106 | linked with the Library has the freedom and the wherewithal to run
107 | that program using a modified version of the Library.
108 |
109 | The precise terms and conditions for copying, distribution and
110 | modification follow. Pay close attention to the difference between a
111 | "work based on the library" and a "work that uses the library". The
112 | former contains code derived from the library, whereas the latter must
113 | be combined with the library in order to run.
114 |
115 | GNU LESSER GENERAL PUBLIC LICENSE
116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
117 |
118 | 0. This License Agreement applies to any software library or other
119 | program which contains a notice placed by the copyright holder or
120 | other authorized party saying it may be distributed under the terms of
121 | this Lesser General Public License (also called "this License").
122 | Each licensee is addressed as "you".
123 |
124 | A "library" means a collection of software functions and/or data
125 | prepared so as to be conveniently linked with application programs
126 | (which use some of those functions and data) to form executables.
127 |
128 | The "Library", below, refers to any such software library or work
129 | which has been distributed under these terms. A "work based on the
130 | Library" means either the Library or any derivative work under
131 | copyright law: that is to say, a work containing the Library or a
132 | portion of it, either verbatim or with modifications and/or translated
133 | straightforwardly into another language. (Hereinafter, translation is
134 | included without limitation in the term "modification".)
135 |
136 | "Source code" for a work means the preferred form of the work for
137 | making modifications to it. For a library, complete source code means
138 | all the source code for all modules it contains, plus any associated
139 | interface definition files, plus the scripts used to control compilation
140 | and installation of the library.
141 |
142 | Activities other than copying, distribution and modification are not
143 | covered by this License; they are outside its scope. The act of
144 | running a program using the Library is not restricted, and output from
145 | such a program is covered only if its contents constitute a work based
146 | on the Library (independent of the use of the Library in a tool for
147 | writing it). Whether that is true depends on what the Library does
148 | and what the program that uses the Library does.
149 |
150 | 1. You may copy and distribute verbatim copies of the Library's
151 | complete source code as you receive it, in any medium, provided that
152 | you conspicuously and appropriately publish on each copy an
153 | appropriate copyright notice and disclaimer of warranty; keep intact
154 | all the notices that refer to this License and to the absence of any
155 | warranty; and distribute a copy of this License along with the
156 | Library.
157 |
158 | You may charge a fee for the physical act of transferring a copy,
159 | and you may at your option offer warranty protection in exchange for a
160 | fee.
161 |
162 | 2. You may modify your copy or copies of the Library or any portion
163 | of it, thus forming a work based on the Library, and copy and
164 | distribute such modifications or work under the terms of Section 1
165 | above, provided that you also meet all of these conditions:
166 |
167 | a) The modified work must itself be a software library.
168 |
169 | b) You must cause the files modified to carry prominent notices
170 | stating that you changed the files and the date of any change.
171 |
172 | c) You must cause the whole of the work to be licensed at no
173 | charge to all third parties under the terms of this License.
174 |
175 | d) If a facility in the modified Library refers to a function or a
176 | table of data to be supplied by an application program that uses
177 | the facility, other than as an argument passed when the facility
178 | is invoked, then you must make a good faith effort to ensure that,
179 | in the event an application does not supply such function or
180 | table, the facility still operates, and performs whatever part of
181 | its purpose remains meaningful.
182 |
183 | (For example, a function in a library to compute square roots has
184 | a purpose that is entirely well-defined independent of the
185 | application. Therefore, Subsection 2d requires that any
186 | application-supplied function or table used by this function must
187 | be optional: if the application does not supply it, the square
188 | root function must still compute square roots.)
189 |
190 | These requirements apply to the modified work as a whole. If
191 | identifiable sections of that work are not derived from the Library,
192 | and can be reasonably considered independent and separate works in
193 | themselves, then this License, and its terms, do not apply to those
194 | sections when you distribute them as separate works. But when you
195 | distribute the same sections as part of a whole which is a work based
196 | on the Library, the distribution of the whole must be on the terms of
197 | this License, whose permissions for other licensees extend to the
198 | entire whole, and thus to each and every part regardless of who wrote
199 | it.
200 |
201 | Thus, it is not the intent of this section to claim rights or contest
202 | your rights to work written entirely by you; rather, the intent is to
203 | exercise the right to control the distribution of derivative or
204 | collective works based on the Library.
205 |
206 | In addition, mere aggregation of another work not based on the Library
207 | with the Library (or with a work based on the Library) on a volume of
208 | a storage or distribution medium does not bring the other work under
209 | the scope of this License.
210 |
211 | 3. You may opt to apply the terms of the ordinary GNU General Public
212 | License instead of this License to a given copy of the Library. To do
213 | this, you must alter all the notices that refer to this License, so
214 | that they refer to the ordinary GNU General Public License, version 2,
215 | instead of to this License. (If a newer version than version 2 of the
216 | ordinary GNU General Public License has appeared, then you can specify
217 | that version instead if you wish.) Do not make any other change in
218 | these notices.
219 |
220 | Once this change is made in a given copy, it is irreversible for
221 | that copy, so the ordinary GNU General Public License applies to all
222 | subsequent copies and derivative works made from that copy.
223 |
224 | This option is useful when you wish to copy part of the code of
225 | the Library into a program that is not a library.
226 |
227 | 4. You may copy and distribute the Library (or a portion or
228 | derivative of it, under Section 2) in object code or executable form
229 | under the terms of Sections 1 and 2 above provided that you accompany
230 | it with the complete corresponding machine-readable source code, which
231 | must be distributed under the terms of Sections 1 and 2 above on a
232 | medium customarily used for software interchange.
233 |
234 | If distribution of object code is made by offering access to copy
235 | from a designated place, then offering equivalent access to copy the
236 | source code from the same place satisfies the requirement to
237 | distribute the source code, even though third parties are not
238 | compelled to copy the source along with the object code.
239 |
240 | 5. A program that contains no derivative of any portion of the
241 | Library, but is designed to work with the Library by being compiled or
242 | linked with it, is called a "work that uses the Library". Such a
243 | work, in isolation, is not a derivative work of the Library, and
244 | therefore falls outside the scope of this License.
245 |
246 | However, linking a "work that uses the Library" with the Library
247 | creates an executable that is a derivative of the Library (because it
248 | contains portions of the Library), rather than a "work that uses the
249 | library". The executable is therefore covered by this License.
250 | Section 6 states terms for distribution of such executables.
251 |
252 | When a "work that uses the Library" uses material from a header file
253 | that is part of the Library, the object code for the work may be a
254 | derivative work of the Library even though the source code is not.
255 | Whether this is true is especially significant if the work can be
256 | linked without the Library, or if the work is itself a library. The
257 | threshold for this to be true is not precisely defined by law.
258 |
259 | If such an object file uses only numerical parameters, data
260 | structure layouts and accessors, and small macros and small inline
261 | functions (ten lines or less in length), then the use of the object
262 | file is unrestricted, regardless of whether it is legally a derivative
263 | work. (Executables containing this object code plus portions of the
264 | Library will still fall under Section 6.)
265 |
266 | Otherwise, if the work is a derivative of the Library, you may
267 | distribute the object code for the work under the terms of Section 6.
268 | Any executables containing that work also fall under Section 6,
269 | whether or not they are linked directly with the Library itself.
270 |
271 | 6. As an exception to the Sections above, you may also combine or
272 | link a "work that uses the Library" with the Library to produce a
273 | work containing portions of the Library, and distribute that work
274 | under terms of your choice, provided that the terms permit
275 | modification of the work for the customer's own use and reverse
276 | engineering for debugging such modifications.
277 |
278 | You must give prominent notice with each copy of the work that the
279 | Library is used in it and that the Library and its use are covered by
280 | this License. You must supply a copy of this License. If the work
281 | during execution displays copyright notices, you must include the
282 | copyright notice for the Library among them, as well as a reference
283 | directing the user to the copy of this License. Also, you must do one
284 | of these things:
285 |
286 | a) Accompany the work with the complete corresponding
287 | machine-readable source code for the Library including whatever
288 | changes were used in the work (which must be distributed under
289 | Sections 1 and 2 above); and, if the work is an executable linked
290 | with the Library, with the complete machine-readable "work that
291 | uses the Library", as object code and/or source code, so that the
292 | user can modify the Library and then relink to produce a modified
293 | executable containing the modified Library. (It is understood
294 | that the user who changes the contents of definitions files in the
295 | Library will not necessarily be able to recompile the application
296 | to use the modified definitions.)
297 |
298 | b) Use a suitable shared library mechanism for linking with the
299 | Library. A suitable mechanism is one that (1) uses at run time a
300 | copy of the library already present on the user's computer system,
301 | rather than copying library functions into the executable, and (2)
302 | will operate properly with a modified version of the library, if
303 | the user installs one, as long as the modified version is
304 | interface-compatible with the version that the work was made with.
305 |
306 | c) Accompany the work with a written offer, valid for at
307 | least three years, to give the same user the materials
308 | specified in Subsection 6a, above, for a charge no more
309 | than the cost of performing this distribution.
310 |
311 | d) If distribution of the work is made by offering access to copy
312 | from a designated place, offer equivalent access to copy the above
313 | specified materials from the same place.
314 |
315 | e) Verify that the user has already received a copy of these
316 | materials or that you have already sent this user a copy.
317 |
318 | For an executable, the required form of the "work that uses the
319 | Library" must include any data and utility programs needed for
320 | reproducing the executable from it. However, as a special exception,
321 | the materials to be distributed need not include anything that is
322 | normally distributed (in either source or binary form) with the major
323 | components (compiler, kernel, and so on) of the operating system on
324 | which the executable runs, unless that component itself accompanies
325 | the executable.
326 |
327 | It may happen that this requirement contradicts the license
328 | restrictions of other proprietary libraries that do not normally
329 | accompany the operating system. Such a contradiction means you cannot
330 | use both them and the Library together in an executable that you
331 | distribute.
332 |
333 | 7. You may place library facilities that are a work based on the
334 | Library side-by-side in a single library together with other library
335 | facilities not covered by this License, and distribute such a combined
336 | library, provided that the separate distribution of the work based on
337 | the Library and of the other library facilities is otherwise
338 | permitted, and provided that you do these two things:
339 |
340 | a) Accompany the combined library with a copy of the same work
341 | based on the Library, uncombined with any other library
342 | facilities. This must be distributed under the terms of the
343 | Sections above.
344 |
345 | b) Give prominent notice with the combined library of the fact
346 | that part of it is a work based on the Library, and explaining
347 | where to find the accompanying uncombined form of the same work.
348 |
349 | 8. You may not copy, modify, sublicense, link with, or distribute
350 | the Library except as expressly provided under this License. Any
351 | attempt otherwise to copy, modify, sublicense, link with, or
352 | distribute the Library is void, and will automatically terminate your
353 | rights under this License. However, parties who have received copies,
354 | or rights, from you under this License will not have their licenses
355 | terminated so long as such parties remain in full compliance.
356 |
357 | 9. You are not required to accept this License, since you have not
358 | signed it. However, nothing else grants you permission to modify or
359 | distribute the Library or its derivative works. These actions are
360 | prohibited by law if you do not accept this License. Therefore, by
361 | modifying or distributing the Library (or any work based on the
362 | Library), you indicate your acceptance of this License to do so, and
363 | all its terms and conditions for copying, distributing or modifying
364 | the Library or works based on it.
365 |
366 | 10. Each time you redistribute the Library (or any work based on the
367 | Library), the recipient automatically receives a license from the
368 | original licensor to copy, distribute, link with or modify the Library
369 | subject to these terms and conditions. You may not impose any further
370 | restrictions on the recipients' exercise of the rights granted herein.
371 | You are not responsible for enforcing compliance by third parties with
372 | this License.
373 |
374 | 11. If, as a consequence of a court judgment or allegation of patent
375 | infringement or for any other reason (not limited to patent issues),
376 | conditions are imposed on you (whether by court order, agreement or
377 | otherwise) that contradict the conditions of this License, they do not
378 | excuse you from the conditions of this License. If you cannot
379 | distribute so as to satisfy simultaneously your obligations under this
380 | License and any other pertinent obligations, then as a consequence you
381 | may not distribute the Library at all. For example, if a patent
382 | license would not permit royalty-free redistribution of the Library by
383 | all those who receive copies directly or indirectly through you, then
384 | the only way you could satisfy both it and this License would be to
385 | refrain entirely from distribution of the Library.
386 |
387 | If any portion of this section is held invalid or unenforceable under any
388 | particular circumstance, the balance of the section is intended to apply,
389 | and the section as a whole is intended to apply in other circumstances.
390 |
391 | It is not the purpose of this section to induce you to infringe any
392 | patents or other property right claims or to contest validity of any
393 | such claims; this section has the sole purpose of protecting the
394 | integrity of the free software distribution system which is
395 | implemented by public license practices. Many people have made
396 | generous contributions to the wide range of software distributed
397 | through that system in reliance on consistent application of that
398 | system; it is up to the author/donor to decide if he or she is willing
399 | to distribute software through any other system and a licensee cannot
400 | impose that choice.
401 |
402 | This section is intended to make thoroughly clear what is believed to
403 | be a consequence of the rest of this License.
404 |
405 | 12. If the distribution and/or use of the Library is restricted in
406 | certain countries either by patents or by copyrighted interfaces, the
407 | original copyright holder who places the Library under this License may add
408 | an explicit geographical distribution limitation excluding those countries,
409 | so that distribution is permitted only in or among countries not thus
410 | excluded. In such case, this License incorporates the limitation as if
411 | written in the body of this License.
412 |
413 | 13. The Free Software Foundation may publish revised and/or new
414 | versions of the Lesser General Public License from time to time.
415 | Such new versions will be similar in spirit to the present version,
416 | but may differ in detail to address new problems or concerns.
417 |
418 | Each version is given a distinguishing version number. If the Library
419 | specifies a version number of this License which applies to it and
420 | "any later version", you have the option of following the terms and
421 | conditions either of that version or of any later version published by
422 | the Free Software Foundation. If the Library does not specify a
423 | license version number, you may choose any version ever published by
424 | the Free Software Foundation.
425 |
426 | 14. If you wish to incorporate parts of the Library into other free
427 | programs whose distribution conditions are incompatible with these,
428 | write to the author to ask for permission. For software which is
429 | copyrighted by the Free Software Foundation, write to the Free
430 | Software Foundation; we sometimes make exceptions for this. Our
431 | decision will be guided by the two goals of preserving the free status
432 | of all derivatives of our free software and of promoting the sharing
433 | and reuse of software generally.
434 |
435 | NO WARRANTY
436 |
437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
446 |
447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
456 | DAMAGES.
457 |
458 | END OF TERMS AND CONDITIONS
459 |
460 | How to Apply These Terms to Your New Libraries
461 |
462 | If you develop a new library, and you want it to be of the greatest
463 | possible use to the public, we recommend making it free software that
464 | everyone can redistribute and change. You can do so by permitting
465 | redistribution under these terms (or, alternatively, under the terms of the
466 | ordinary General Public License).
467 |
468 | To apply these terms, attach the following notices to the library. It is
469 | safest to attach them to the start of each source file to most effectively
470 | convey the exclusion of warranty; and each file should have at least the
471 | "copyright" line and a pointer to where the full notice is found.
472 |
473 | {description}
474 | Copyright (C) {year} {fullname}
475 |
476 | This library is free software; you can redistribute it and/or
477 | modify it under the terms of the GNU Lesser General Public
478 | License as published by the Free Software Foundation; either
479 | version 2.1 of the License, or (at your option) any later version.
480 |
481 | This library is distributed in the hope that it will be useful,
482 | but WITHOUT ANY WARRANTY; without even the implied warranty of
483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
484 | Lesser General Public License for more details.
485 |
486 | You should have received a copy of the GNU Lesser General Public
487 | License along with this library; if not, write to the Free Software
488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
489 | USA
490 |
491 | Also add information on how to contact you by electronic and paper mail.
492 |
493 | You should also get your employer (if you work as a programmer) or your
494 | school, if any, to sign a "copyright disclaimer" for the library, if
495 | necessary. Here is a sample; alter the names:
496 |
497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the
498 | library `Frob' (a library for tweaking knobs) written by James Random
499 | Hacker.
500 |
501 | {signature of Ty Coon}, 1 April 1990
502 | Ty Coon, President of Vice
503 |
504 | That's all there is to it!
505 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "32a7ce6306a18c673ac7ceda77e01f56",
8 | "packages": [],
9 | "packages-dev": [
10 | {
11 | "name": "myclabs/deep-copy",
12 | "version": "1.13.4",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/myclabs/DeepCopy.git",
16 | "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
21 | "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "php": "^7.1 || ^8.0"
26 | },
27 | "conflict": {
28 | "doctrine/collections": "<1.6.8",
29 | "doctrine/common": "<2.13.3 || >=3 <3.2.2"
30 | },
31 | "require-dev": {
32 | "doctrine/collections": "^1.6.8",
33 | "doctrine/common": "^2.13.3 || ^3.2.2",
34 | "phpspec/prophecy": "^1.10",
35 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
36 | },
37 | "type": "library",
38 | "autoload": {
39 | "files": [
40 | "src/DeepCopy/deep_copy.php"
41 | ],
42 | "psr-4": {
43 | "DeepCopy\\": "src/DeepCopy/"
44 | }
45 | },
46 | "notification-url": "https://packagist.org/downloads/",
47 | "license": [
48 | "MIT"
49 | ],
50 | "description": "Create deep copies (clones) of your objects",
51 | "keywords": [
52 | "clone",
53 | "copy",
54 | "duplicate",
55 | "object",
56 | "object graph"
57 | ],
58 | "support": {
59 | "issues": "https://github.com/myclabs/DeepCopy/issues",
60 | "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
61 | },
62 | "funding": [
63 | {
64 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
65 | "type": "tidelift"
66 | }
67 | ],
68 | "time": "2025-08-01T08:46:24+00:00"
69 | },
70 | {
71 | "name": "nikic/php-parser",
72 | "version": "v5.6.1",
73 | "source": {
74 | "type": "git",
75 | "url": "https://github.com/nikic/PHP-Parser.git",
76 | "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2"
77 | },
78 | "dist": {
79 | "type": "zip",
80 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
81 | "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
82 | "shasum": ""
83 | },
84 | "require": {
85 | "ext-ctype": "*",
86 | "ext-json": "*",
87 | "ext-tokenizer": "*",
88 | "php": ">=7.4"
89 | },
90 | "require-dev": {
91 | "ircmaxell/php-yacc": "^0.0.7",
92 | "phpunit/phpunit": "^9.0"
93 | },
94 | "bin": [
95 | "bin/php-parse"
96 | ],
97 | "type": "library",
98 | "extra": {
99 | "branch-alias": {
100 | "dev-master": "5.x-dev"
101 | }
102 | },
103 | "autoload": {
104 | "psr-4": {
105 | "PhpParser\\": "lib/PhpParser"
106 | }
107 | },
108 | "notification-url": "https://packagist.org/downloads/",
109 | "license": [
110 | "BSD-3-Clause"
111 | ],
112 | "authors": [
113 | {
114 | "name": "Nikita Popov"
115 | }
116 | ],
117 | "description": "A PHP parser written in PHP",
118 | "keywords": [
119 | "parser",
120 | "php"
121 | ],
122 | "support": {
123 | "issues": "https://github.com/nikic/PHP-Parser/issues",
124 | "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1"
125 | },
126 | "time": "2025-08-13T20:13:15+00:00"
127 | },
128 | {
129 | "name": "phar-io/manifest",
130 | "version": "2.0.4",
131 | "source": {
132 | "type": "git",
133 | "url": "https://github.com/phar-io/manifest.git",
134 | "reference": "54750ef60c58e43759730615a392c31c80e23176"
135 | },
136 | "dist": {
137 | "type": "zip",
138 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
139 | "reference": "54750ef60c58e43759730615a392c31c80e23176",
140 | "shasum": ""
141 | },
142 | "require": {
143 | "ext-dom": "*",
144 | "ext-libxml": "*",
145 | "ext-phar": "*",
146 | "ext-xmlwriter": "*",
147 | "phar-io/version": "^3.0.1",
148 | "php": "^7.2 || ^8.0"
149 | },
150 | "type": "library",
151 | "extra": {
152 | "branch-alias": {
153 | "dev-master": "2.0.x-dev"
154 | }
155 | },
156 | "autoload": {
157 | "classmap": [
158 | "src/"
159 | ]
160 | },
161 | "notification-url": "https://packagist.org/downloads/",
162 | "license": [
163 | "BSD-3-Clause"
164 | ],
165 | "authors": [
166 | {
167 | "name": "Arne Blankerts",
168 | "email": "arne@blankerts.de",
169 | "role": "Developer"
170 | },
171 | {
172 | "name": "Sebastian Heuer",
173 | "email": "sebastian@phpeople.de",
174 | "role": "Developer"
175 | },
176 | {
177 | "name": "Sebastian Bergmann",
178 | "email": "sebastian@phpunit.de",
179 | "role": "Developer"
180 | }
181 | ],
182 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
183 | "support": {
184 | "issues": "https://github.com/phar-io/manifest/issues",
185 | "source": "https://github.com/phar-io/manifest/tree/2.0.4"
186 | },
187 | "funding": [
188 | {
189 | "url": "https://github.com/theseer",
190 | "type": "github"
191 | }
192 | ],
193 | "time": "2024-03-03T12:33:53+00:00"
194 | },
195 | {
196 | "name": "phar-io/version",
197 | "version": "3.2.1",
198 | "source": {
199 | "type": "git",
200 | "url": "https://github.com/phar-io/version.git",
201 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
202 | },
203 | "dist": {
204 | "type": "zip",
205 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
206 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
207 | "shasum": ""
208 | },
209 | "require": {
210 | "php": "^7.2 || ^8.0"
211 | },
212 | "type": "library",
213 | "autoload": {
214 | "classmap": [
215 | "src/"
216 | ]
217 | },
218 | "notification-url": "https://packagist.org/downloads/",
219 | "license": [
220 | "BSD-3-Clause"
221 | ],
222 | "authors": [
223 | {
224 | "name": "Arne Blankerts",
225 | "email": "arne@blankerts.de",
226 | "role": "Developer"
227 | },
228 | {
229 | "name": "Sebastian Heuer",
230 | "email": "sebastian@phpeople.de",
231 | "role": "Developer"
232 | },
233 | {
234 | "name": "Sebastian Bergmann",
235 | "email": "sebastian@phpunit.de",
236 | "role": "Developer"
237 | }
238 | ],
239 | "description": "Library for handling version information and constraints",
240 | "support": {
241 | "issues": "https://github.com/phar-io/version/issues",
242 | "source": "https://github.com/phar-io/version/tree/3.2.1"
243 | },
244 | "time": "2022-02-21T01:04:05+00:00"
245 | },
246 | {
247 | "name": "phpstan/phpstan",
248 | "version": "2.1.29",
249 | "source": {
250 | "type": "git",
251 | "url": "https://github.com/phpstan/phpstan-phar-composer-source.git",
252 | "reference": "git"
253 | },
254 | "dist": {
255 | "type": "zip",
256 | "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d618573eed4a1b6b75e37b2e0b65ac65c885d88e",
257 | "reference": "d618573eed4a1b6b75e37b2e0b65ac65c885d88e",
258 | "shasum": ""
259 | },
260 | "require": {
261 | "php": "^7.4|^8.0"
262 | },
263 | "conflict": {
264 | "phpstan/phpstan-shim": "*"
265 | },
266 | "bin": [
267 | "phpstan",
268 | "phpstan.phar"
269 | ],
270 | "type": "library",
271 | "autoload": {
272 | "files": [
273 | "bootstrap.php"
274 | ]
275 | },
276 | "notification-url": "https://packagist.org/downloads/",
277 | "license": [
278 | "MIT"
279 | ],
280 | "description": "PHPStan - PHP Static Analysis Tool",
281 | "keywords": [
282 | "dev",
283 | "static analysis"
284 | ],
285 | "support": {
286 | "docs": "https://phpstan.org/user-guide/getting-started",
287 | "forum": "https://github.com/phpstan/phpstan/discussions",
288 | "issues": "https://github.com/phpstan/phpstan/issues",
289 | "security": "https://github.com/phpstan/phpstan/security/policy",
290 | "source": "https://github.com/phpstan/phpstan-src"
291 | },
292 | "funding": [
293 | {
294 | "url": "https://github.com/ondrejmirtes",
295 | "type": "github"
296 | },
297 | {
298 | "url": "https://github.com/phpstan",
299 | "type": "github"
300 | }
301 | ],
302 | "time": "2025-09-25T06:58:18+00:00"
303 | },
304 | {
305 | "name": "phpstan/phpstan-strict-rules",
306 | "version": "2.0.7",
307 | "source": {
308 | "type": "git",
309 | "url": "https://github.com/phpstan/phpstan-strict-rules.git",
310 | "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538"
311 | },
312 | "dist": {
313 | "type": "zip",
314 | "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538",
315 | "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538",
316 | "shasum": ""
317 | },
318 | "require": {
319 | "php": "^7.4 || ^8.0",
320 | "phpstan/phpstan": "^2.1.29"
321 | },
322 | "require-dev": {
323 | "php-parallel-lint/php-parallel-lint": "^1.2",
324 | "phpstan/phpstan-deprecation-rules": "^2.0",
325 | "phpstan/phpstan-phpunit": "^2.0",
326 | "phpunit/phpunit": "^9.6"
327 | },
328 | "type": "phpstan-extension",
329 | "extra": {
330 | "phpstan": {
331 | "includes": [
332 | "rules.neon"
333 | ]
334 | }
335 | },
336 | "autoload": {
337 | "psr-4": {
338 | "PHPStan\\": "src/"
339 | }
340 | },
341 | "notification-url": "https://packagist.org/downloads/",
342 | "license": [
343 | "MIT"
344 | ],
345 | "description": "Extra strict and opinionated rules for PHPStan",
346 | "support": {
347 | "issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
348 | "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7"
349 | },
350 | "time": "2025-09-26T11:19:08+00:00"
351 | },
352 | {
353 | "name": "phpunit/php-code-coverage",
354 | "version": "10.1.16",
355 | "source": {
356 | "type": "git",
357 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
358 | "reference": "7e308268858ed6baedc8704a304727d20bc07c77"
359 | },
360 | "dist": {
361 | "type": "zip",
362 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
363 | "reference": "7e308268858ed6baedc8704a304727d20bc07c77",
364 | "shasum": ""
365 | },
366 | "require": {
367 | "ext-dom": "*",
368 | "ext-libxml": "*",
369 | "ext-xmlwriter": "*",
370 | "nikic/php-parser": "^4.19.1 || ^5.1.0",
371 | "php": ">=8.1",
372 | "phpunit/php-file-iterator": "^4.1.0",
373 | "phpunit/php-text-template": "^3.0.1",
374 | "sebastian/code-unit-reverse-lookup": "^3.0.0",
375 | "sebastian/complexity": "^3.2.0",
376 | "sebastian/environment": "^6.1.0",
377 | "sebastian/lines-of-code": "^2.0.2",
378 | "sebastian/version": "^4.0.1",
379 | "theseer/tokenizer": "^1.2.3"
380 | },
381 | "require-dev": {
382 | "phpunit/phpunit": "^10.1"
383 | },
384 | "suggest": {
385 | "ext-pcov": "PHP extension that provides line coverage",
386 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
387 | },
388 | "type": "library",
389 | "extra": {
390 | "branch-alias": {
391 | "dev-main": "10.1.x-dev"
392 | }
393 | },
394 | "autoload": {
395 | "classmap": [
396 | "src/"
397 | ]
398 | },
399 | "notification-url": "https://packagist.org/downloads/",
400 | "license": [
401 | "BSD-3-Clause"
402 | ],
403 | "authors": [
404 | {
405 | "name": "Sebastian Bergmann",
406 | "email": "sebastian@phpunit.de",
407 | "role": "lead"
408 | }
409 | ],
410 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
411 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
412 | "keywords": [
413 | "coverage",
414 | "testing",
415 | "xunit"
416 | ],
417 | "support": {
418 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
419 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
420 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16"
421 | },
422 | "funding": [
423 | {
424 | "url": "https://github.com/sebastianbergmann",
425 | "type": "github"
426 | }
427 | ],
428 | "time": "2024-08-22T04:31:57+00:00"
429 | },
430 | {
431 | "name": "phpunit/php-file-iterator",
432 | "version": "4.1.0",
433 | "source": {
434 | "type": "git",
435 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
436 | "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
437 | },
438 | "dist": {
439 | "type": "zip",
440 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c",
441 | "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
442 | "shasum": ""
443 | },
444 | "require": {
445 | "php": ">=8.1"
446 | },
447 | "require-dev": {
448 | "phpunit/phpunit": "^10.0"
449 | },
450 | "type": "library",
451 | "extra": {
452 | "branch-alias": {
453 | "dev-main": "4.0-dev"
454 | }
455 | },
456 | "autoload": {
457 | "classmap": [
458 | "src/"
459 | ]
460 | },
461 | "notification-url": "https://packagist.org/downloads/",
462 | "license": [
463 | "BSD-3-Clause"
464 | ],
465 | "authors": [
466 | {
467 | "name": "Sebastian Bergmann",
468 | "email": "sebastian@phpunit.de",
469 | "role": "lead"
470 | }
471 | ],
472 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
473 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
474 | "keywords": [
475 | "filesystem",
476 | "iterator"
477 | ],
478 | "support": {
479 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
480 | "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
481 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0"
482 | },
483 | "funding": [
484 | {
485 | "url": "https://github.com/sebastianbergmann",
486 | "type": "github"
487 | }
488 | ],
489 | "time": "2023-08-31T06:24:48+00:00"
490 | },
491 | {
492 | "name": "phpunit/php-invoker",
493 | "version": "4.0.0",
494 | "source": {
495 | "type": "git",
496 | "url": "https://github.com/sebastianbergmann/php-invoker.git",
497 | "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7"
498 | },
499 | "dist": {
500 | "type": "zip",
501 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
502 | "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
503 | "shasum": ""
504 | },
505 | "require": {
506 | "php": ">=8.1"
507 | },
508 | "require-dev": {
509 | "ext-pcntl": "*",
510 | "phpunit/phpunit": "^10.0"
511 | },
512 | "suggest": {
513 | "ext-pcntl": "*"
514 | },
515 | "type": "library",
516 | "extra": {
517 | "branch-alias": {
518 | "dev-main": "4.0-dev"
519 | }
520 | },
521 | "autoload": {
522 | "classmap": [
523 | "src/"
524 | ]
525 | },
526 | "notification-url": "https://packagist.org/downloads/",
527 | "license": [
528 | "BSD-3-Clause"
529 | ],
530 | "authors": [
531 | {
532 | "name": "Sebastian Bergmann",
533 | "email": "sebastian@phpunit.de",
534 | "role": "lead"
535 | }
536 | ],
537 | "description": "Invoke callables with a timeout",
538 | "homepage": "https://github.com/sebastianbergmann/php-invoker/",
539 | "keywords": [
540 | "process"
541 | ],
542 | "support": {
543 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
544 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0"
545 | },
546 | "funding": [
547 | {
548 | "url": "https://github.com/sebastianbergmann",
549 | "type": "github"
550 | }
551 | ],
552 | "time": "2023-02-03T06:56:09+00:00"
553 | },
554 | {
555 | "name": "phpunit/php-text-template",
556 | "version": "3.0.1",
557 | "source": {
558 | "type": "git",
559 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
560 | "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
561 | },
562 | "dist": {
563 | "type": "zip",
564 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748",
565 | "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
566 | "shasum": ""
567 | },
568 | "require": {
569 | "php": ">=8.1"
570 | },
571 | "require-dev": {
572 | "phpunit/phpunit": "^10.0"
573 | },
574 | "type": "library",
575 | "extra": {
576 | "branch-alias": {
577 | "dev-main": "3.0-dev"
578 | }
579 | },
580 | "autoload": {
581 | "classmap": [
582 | "src/"
583 | ]
584 | },
585 | "notification-url": "https://packagist.org/downloads/",
586 | "license": [
587 | "BSD-3-Clause"
588 | ],
589 | "authors": [
590 | {
591 | "name": "Sebastian Bergmann",
592 | "email": "sebastian@phpunit.de",
593 | "role": "lead"
594 | }
595 | ],
596 | "description": "Simple template engine.",
597 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
598 | "keywords": [
599 | "template"
600 | ],
601 | "support": {
602 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
603 | "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
604 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1"
605 | },
606 | "funding": [
607 | {
608 | "url": "https://github.com/sebastianbergmann",
609 | "type": "github"
610 | }
611 | ],
612 | "time": "2023-08-31T14:07:24+00:00"
613 | },
614 | {
615 | "name": "phpunit/php-timer",
616 | "version": "6.0.0",
617 | "source": {
618 | "type": "git",
619 | "url": "https://github.com/sebastianbergmann/php-timer.git",
620 | "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d"
621 | },
622 | "dist": {
623 | "type": "zip",
624 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d",
625 | "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d",
626 | "shasum": ""
627 | },
628 | "require": {
629 | "php": ">=8.1"
630 | },
631 | "require-dev": {
632 | "phpunit/phpunit": "^10.0"
633 | },
634 | "type": "library",
635 | "extra": {
636 | "branch-alias": {
637 | "dev-main": "6.0-dev"
638 | }
639 | },
640 | "autoload": {
641 | "classmap": [
642 | "src/"
643 | ]
644 | },
645 | "notification-url": "https://packagist.org/downloads/",
646 | "license": [
647 | "BSD-3-Clause"
648 | ],
649 | "authors": [
650 | {
651 | "name": "Sebastian Bergmann",
652 | "email": "sebastian@phpunit.de",
653 | "role": "lead"
654 | }
655 | ],
656 | "description": "Utility class for timing",
657 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
658 | "keywords": [
659 | "timer"
660 | ],
661 | "support": {
662 | "issues": "https://github.com/sebastianbergmann/php-timer/issues",
663 | "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0"
664 | },
665 | "funding": [
666 | {
667 | "url": "https://github.com/sebastianbergmann",
668 | "type": "github"
669 | }
670 | ],
671 | "time": "2023-02-03T06:57:52+00:00"
672 | },
673 | {
674 | "name": "phpunit/phpunit",
675 | "version": "10.5.58",
676 | "source": {
677 | "type": "git",
678 | "url": "https://github.com/sebastianbergmann/phpunit.git",
679 | "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca"
680 | },
681 | "dist": {
682 | "type": "zip",
683 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca",
684 | "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca",
685 | "shasum": ""
686 | },
687 | "require": {
688 | "ext-dom": "*",
689 | "ext-json": "*",
690 | "ext-libxml": "*",
691 | "ext-mbstring": "*",
692 | "ext-xml": "*",
693 | "ext-xmlwriter": "*",
694 | "myclabs/deep-copy": "^1.13.4",
695 | "phar-io/manifest": "^2.0.4",
696 | "phar-io/version": "^3.2.1",
697 | "php": ">=8.1",
698 | "phpunit/php-code-coverage": "^10.1.16",
699 | "phpunit/php-file-iterator": "^4.1.0",
700 | "phpunit/php-invoker": "^4.0.0",
701 | "phpunit/php-text-template": "^3.0.1",
702 | "phpunit/php-timer": "^6.0.0",
703 | "sebastian/cli-parser": "^2.0.1",
704 | "sebastian/code-unit": "^2.0.0",
705 | "sebastian/comparator": "^5.0.4",
706 | "sebastian/diff": "^5.1.1",
707 | "sebastian/environment": "^6.1.0",
708 | "sebastian/exporter": "^5.1.4",
709 | "sebastian/global-state": "^6.0.2",
710 | "sebastian/object-enumerator": "^5.0.0",
711 | "sebastian/recursion-context": "^5.0.1",
712 | "sebastian/type": "^4.0.0",
713 | "sebastian/version": "^4.0.1"
714 | },
715 | "suggest": {
716 | "ext-soap": "To be able to generate mocks based on WSDL files"
717 | },
718 | "bin": [
719 | "phpunit"
720 | ],
721 | "type": "library",
722 | "extra": {
723 | "branch-alias": {
724 | "dev-main": "10.5-dev"
725 | }
726 | },
727 | "autoload": {
728 | "files": [
729 | "src/Framework/Assert/Functions.php"
730 | ],
731 | "classmap": [
732 | "src/"
733 | ]
734 | },
735 | "notification-url": "https://packagist.org/downloads/",
736 | "license": [
737 | "BSD-3-Clause"
738 | ],
739 | "authors": [
740 | {
741 | "name": "Sebastian Bergmann",
742 | "email": "sebastian@phpunit.de",
743 | "role": "lead"
744 | }
745 | ],
746 | "description": "The PHP Unit Testing framework.",
747 | "homepage": "https://phpunit.de/",
748 | "keywords": [
749 | "phpunit",
750 | "testing",
751 | "xunit"
752 | ],
753 | "support": {
754 | "issues": "https://github.com/sebastianbergmann/phpunit/issues",
755 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
756 | "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58"
757 | },
758 | "funding": [
759 | {
760 | "url": "https://phpunit.de/sponsors.html",
761 | "type": "custom"
762 | },
763 | {
764 | "url": "https://github.com/sebastianbergmann",
765 | "type": "github"
766 | },
767 | {
768 | "url": "https://liberapay.com/sebastianbergmann",
769 | "type": "liberapay"
770 | },
771 | {
772 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
773 | "type": "thanks_dev"
774 | },
775 | {
776 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
777 | "type": "tidelift"
778 | }
779 | ],
780 | "time": "2025-09-28T12:04:46+00:00"
781 | },
782 | {
783 | "name": "sebastian/cli-parser",
784 | "version": "2.0.1",
785 | "source": {
786 | "type": "git",
787 | "url": "https://github.com/sebastianbergmann/cli-parser.git",
788 | "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
789 | },
790 | "dist": {
791 | "type": "zip",
792 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
793 | "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
794 | "shasum": ""
795 | },
796 | "require": {
797 | "php": ">=8.1"
798 | },
799 | "require-dev": {
800 | "phpunit/phpunit": "^10.0"
801 | },
802 | "type": "library",
803 | "extra": {
804 | "branch-alias": {
805 | "dev-main": "2.0-dev"
806 | }
807 | },
808 | "autoload": {
809 | "classmap": [
810 | "src/"
811 | ]
812 | },
813 | "notification-url": "https://packagist.org/downloads/",
814 | "license": [
815 | "BSD-3-Clause"
816 | ],
817 | "authors": [
818 | {
819 | "name": "Sebastian Bergmann",
820 | "email": "sebastian@phpunit.de",
821 | "role": "lead"
822 | }
823 | ],
824 | "description": "Library for parsing CLI options",
825 | "homepage": "https://github.com/sebastianbergmann/cli-parser",
826 | "support": {
827 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
828 | "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
829 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1"
830 | },
831 | "funding": [
832 | {
833 | "url": "https://github.com/sebastianbergmann",
834 | "type": "github"
835 | }
836 | ],
837 | "time": "2024-03-02T07:12:49+00:00"
838 | },
839 | {
840 | "name": "sebastian/code-unit",
841 | "version": "2.0.0",
842 | "source": {
843 | "type": "git",
844 | "url": "https://github.com/sebastianbergmann/code-unit.git",
845 | "reference": "a81fee9eef0b7a76af11d121767abc44c104e503"
846 | },
847 | "dist": {
848 | "type": "zip",
849 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503",
850 | "reference": "a81fee9eef0b7a76af11d121767abc44c104e503",
851 | "shasum": ""
852 | },
853 | "require": {
854 | "php": ">=8.1"
855 | },
856 | "require-dev": {
857 | "phpunit/phpunit": "^10.0"
858 | },
859 | "type": "library",
860 | "extra": {
861 | "branch-alias": {
862 | "dev-main": "2.0-dev"
863 | }
864 | },
865 | "autoload": {
866 | "classmap": [
867 | "src/"
868 | ]
869 | },
870 | "notification-url": "https://packagist.org/downloads/",
871 | "license": [
872 | "BSD-3-Clause"
873 | ],
874 | "authors": [
875 | {
876 | "name": "Sebastian Bergmann",
877 | "email": "sebastian@phpunit.de",
878 | "role": "lead"
879 | }
880 | ],
881 | "description": "Collection of value objects that represent the PHP code units",
882 | "homepage": "https://github.com/sebastianbergmann/code-unit",
883 | "support": {
884 | "issues": "https://github.com/sebastianbergmann/code-unit/issues",
885 | "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0"
886 | },
887 | "funding": [
888 | {
889 | "url": "https://github.com/sebastianbergmann",
890 | "type": "github"
891 | }
892 | ],
893 | "time": "2023-02-03T06:58:43+00:00"
894 | },
895 | {
896 | "name": "sebastian/code-unit-reverse-lookup",
897 | "version": "3.0.0",
898 | "source": {
899 | "type": "git",
900 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
901 | "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d"
902 | },
903 | "dist": {
904 | "type": "zip",
905 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
906 | "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
907 | "shasum": ""
908 | },
909 | "require": {
910 | "php": ">=8.1"
911 | },
912 | "require-dev": {
913 | "phpunit/phpunit": "^10.0"
914 | },
915 | "type": "library",
916 | "extra": {
917 | "branch-alias": {
918 | "dev-main": "3.0-dev"
919 | }
920 | },
921 | "autoload": {
922 | "classmap": [
923 | "src/"
924 | ]
925 | },
926 | "notification-url": "https://packagist.org/downloads/",
927 | "license": [
928 | "BSD-3-Clause"
929 | ],
930 | "authors": [
931 | {
932 | "name": "Sebastian Bergmann",
933 | "email": "sebastian@phpunit.de"
934 | }
935 | ],
936 | "description": "Looks up which function or method a line of code belongs to",
937 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
938 | "support": {
939 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
940 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0"
941 | },
942 | "funding": [
943 | {
944 | "url": "https://github.com/sebastianbergmann",
945 | "type": "github"
946 | }
947 | ],
948 | "time": "2023-02-03T06:59:15+00:00"
949 | },
950 | {
951 | "name": "sebastian/comparator",
952 | "version": "5.0.4",
953 | "source": {
954 | "type": "git",
955 | "url": "https://github.com/sebastianbergmann/comparator.git",
956 | "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
957 | },
958 | "dist": {
959 | "type": "zip",
960 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
961 | "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
962 | "shasum": ""
963 | },
964 | "require": {
965 | "ext-dom": "*",
966 | "ext-mbstring": "*",
967 | "php": ">=8.1",
968 | "sebastian/diff": "^5.0",
969 | "sebastian/exporter": "^5.0"
970 | },
971 | "require-dev": {
972 | "phpunit/phpunit": "^10.5"
973 | },
974 | "type": "library",
975 | "extra": {
976 | "branch-alias": {
977 | "dev-main": "5.0-dev"
978 | }
979 | },
980 | "autoload": {
981 | "classmap": [
982 | "src/"
983 | ]
984 | },
985 | "notification-url": "https://packagist.org/downloads/",
986 | "license": [
987 | "BSD-3-Clause"
988 | ],
989 | "authors": [
990 | {
991 | "name": "Sebastian Bergmann",
992 | "email": "sebastian@phpunit.de"
993 | },
994 | {
995 | "name": "Jeff Welch",
996 | "email": "whatthejeff@gmail.com"
997 | },
998 | {
999 | "name": "Volker Dusch",
1000 | "email": "github@wallbash.com"
1001 | },
1002 | {
1003 | "name": "Bernhard Schussek",
1004 | "email": "bschussek@2bepublished.at"
1005 | }
1006 | ],
1007 | "description": "Provides the functionality to compare PHP values for equality",
1008 | "homepage": "https://github.com/sebastianbergmann/comparator",
1009 | "keywords": [
1010 | "comparator",
1011 | "compare",
1012 | "equality"
1013 | ],
1014 | "support": {
1015 | "issues": "https://github.com/sebastianbergmann/comparator/issues",
1016 | "security": "https://github.com/sebastianbergmann/comparator/security/policy",
1017 | "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
1018 | },
1019 | "funding": [
1020 | {
1021 | "url": "https://github.com/sebastianbergmann",
1022 | "type": "github"
1023 | },
1024 | {
1025 | "url": "https://liberapay.com/sebastianbergmann",
1026 | "type": "liberapay"
1027 | },
1028 | {
1029 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1030 | "type": "thanks_dev"
1031 | },
1032 | {
1033 | "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
1034 | "type": "tidelift"
1035 | }
1036 | ],
1037 | "time": "2025-09-07T05:25:07+00:00"
1038 | },
1039 | {
1040 | "name": "sebastian/complexity",
1041 | "version": "3.2.0",
1042 | "source": {
1043 | "type": "git",
1044 | "url": "https://github.com/sebastianbergmann/complexity.git",
1045 | "reference": "68ff824baeae169ec9f2137158ee529584553799"
1046 | },
1047 | "dist": {
1048 | "type": "zip",
1049 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
1050 | "reference": "68ff824baeae169ec9f2137158ee529584553799",
1051 | "shasum": ""
1052 | },
1053 | "require": {
1054 | "nikic/php-parser": "^4.18 || ^5.0",
1055 | "php": ">=8.1"
1056 | },
1057 | "require-dev": {
1058 | "phpunit/phpunit": "^10.0"
1059 | },
1060 | "type": "library",
1061 | "extra": {
1062 | "branch-alias": {
1063 | "dev-main": "3.2-dev"
1064 | }
1065 | },
1066 | "autoload": {
1067 | "classmap": [
1068 | "src/"
1069 | ]
1070 | },
1071 | "notification-url": "https://packagist.org/downloads/",
1072 | "license": [
1073 | "BSD-3-Clause"
1074 | ],
1075 | "authors": [
1076 | {
1077 | "name": "Sebastian Bergmann",
1078 | "email": "sebastian@phpunit.de",
1079 | "role": "lead"
1080 | }
1081 | ],
1082 | "description": "Library for calculating the complexity of PHP code units",
1083 | "homepage": "https://github.com/sebastianbergmann/complexity",
1084 | "support": {
1085 | "issues": "https://github.com/sebastianbergmann/complexity/issues",
1086 | "security": "https://github.com/sebastianbergmann/complexity/security/policy",
1087 | "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
1088 | },
1089 | "funding": [
1090 | {
1091 | "url": "https://github.com/sebastianbergmann",
1092 | "type": "github"
1093 | }
1094 | ],
1095 | "time": "2023-12-21T08:37:17+00:00"
1096 | },
1097 | {
1098 | "name": "sebastian/diff",
1099 | "version": "5.1.1",
1100 | "source": {
1101 | "type": "git",
1102 | "url": "https://github.com/sebastianbergmann/diff.git",
1103 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
1104 | },
1105 | "dist": {
1106 | "type": "zip",
1107 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
1108 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
1109 | "shasum": ""
1110 | },
1111 | "require": {
1112 | "php": ">=8.1"
1113 | },
1114 | "require-dev": {
1115 | "phpunit/phpunit": "^10.0",
1116 | "symfony/process": "^6.4"
1117 | },
1118 | "type": "library",
1119 | "extra": {
1120 | "branch-alias": {
1121 | "dev-main": "5.1-dev"
1122 | }
1123 | },
1124 | "autoload": {
1125 | "classmap": [
1126 | "src/"
1127 | ]
1128 | },
1129 | "notification-url": "https://packagist.org/downloads/",
1130 | "license": [
1131 | "BSD-3-Clause"
1132 | ],
1133 | "authors": [
1134 | {
1135 | "name": "Sebastian Bergmann",
1136 | "email": "sebastian@phpunit.de"
1137 | },
1138 | {
1139 | "name": "Kore Nordmann",
1140 | "email": "mail@kore-nordmann.de"
1141 | }
1142 | ],
1143 | "description": "Diff implementation",
1144 | "homepage": "https://github.com/sebastianbergmann/diff",
1145 | "keywords": [
1146 | "diff",
1147 | "udiff",
1148 | "unidiff",
1149 | "unified diff"
1150 | ],
1151 | "support": {
1152 | "issues": "https://github.com/sebastianbergmann/diff/issues",
1153 | "security": "https://github.com/sebastianbergmann/diff/security/policy",
1154 | "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
1155 | },
1156 | "funding": [
1157 | {
1158 | "url": "https://github.com/sebastianbergmann",
1159 | "type": "github"
1160 | }
1161 | ],
1162 | "time": "2024-03-02T07:15:17+00:00"
1163 | },
1164 | {
1165 | "name": "sebastian/environment",
1166 | "version": "6.1.0",
1167 | "source": {
1168 | "type": "git",
1169 | "url": "https://github.com/sebastianbergmann/environment.git",
1170 | "reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
1171 | },
1172 | "dist": {
1173 | "type": "zip",
1174 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
1175 | "reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
1176 | "shasum": ""
1177 | },
1178 | "require": {
1179 | "php": ">=8.1"
1180 | },
1181 | "require-dev": {
1182 | "phpunit/phpunit": "^10.0"
1183 | },
1184 | "suggest": {
1185 | "ext-posix": "*"
1186 | },
1187 | "type": "library",
1188 | "extra": {
1189 | "branch-alias": {
1190 | "dev-main": "6.1-dev"
1191 | }
1192 | },
1193 | "autoload": {
1194 | "classmap": [
1195 | "src/"
1196 | ]
1197 | },
1198 | "notification-url": "https://packagist.org/downloads/",
1199 | "license": [
1200 | "BSD-3-Clause"
1201 | ],
1202 | "authors": [
1203 | {
1204 | "name": "Sebastian Bergmann",
1205 | "email": "sebastian@phpunit.de"
1206 | }
1207 | ],
1208 | "description": "Provides functionality to handle HHVM/PHP environments",
1209 | "homepage": "https://github.com/sebastianbergmann/environment",
1210 | "keywords": [
1211 | "Xdebug",
1212 | "environment",
1213 | "hhvm"
1214 | ],
1215 | "support": {
1216 | "issues": "https://github.com/sebastianbergmann/environment/issues",
1217 | "security": "https://github.com/sebastianbergmann/environment/security/policy",
1218 | "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
1219 | },
1220 | "funding": [
1221 | {
1222 | "url": "https://github.com/sebastianbergmann",
1223 | "type": "github"
1224 | }
1225 | ],
1226 | "time": "2024-03-23T08:47:14+00:00"
1227 | },
1228 | {
1229 | "name": "sebastian/exporter",
1230 | "version": "5.1.4",
1231 | "source": {
1232 | "type": "git",
1233 | "url": "https://github.com/sebastianbergmann/exporter.git",
1234 | "reference": "0735b90f4da94969541dac1da743446e276defa6"
1235 | },
1236 | "dist": {
1237 | "type": "zip",
1238 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6",
1239 | "reference": "0735b90f4da94969541dac1da743446e276defa6",
1240 | "shasum": ""
1241 | },
1242 | "require": {
1243 | "ext-mbstring": "*",
1244 | "php": ">=8.1",
1245 | "sebastian/recursion-context": "^5.0"
1246 | },
1247 | "require-dev": {
1248 | "phpunit/phpunit": "^10.5"
1249 | },
1250 | "type": "library",
1251 | "extra": {
1252 | "branch-alias": {
1253 | "dev-main": "5.1-dev"
1254 | }
1255 | },
1256 | "autoload": {
1257 | "classmap": [
1258 | "src/"
1259 | ]
1260 | },
1261 | "notification-url": "https://packagist.org/downloads/",
1262 | "license": [
1263 | "BSD-3-Clause"
1264 | ],
1265 | "authors": [
1266 | {
1267 | "name": "Sebastian Bergmann",
1268 | "email": "sebastian@phpunit.de"
1269 | },
1270 | {
1271 | "name": "Jeff Welch",
1272 | "email": "whatthejeff@gmail.com"
1273 | },
1274 | {
1275 | "name": "Volker Dusch",
1276 | "email": "github@wallbash.com"
1277 | },
1278 | {
1279 | "name": "Adam Harvey",
1280 | "email": "aharvey@php.net"
1281 | },
1282 | {
1283 | "name": "Bernhard Schussek",
1284 | "email": "bschussek@gmail.com"
1285 | }
1286 | ],
1287 | "description": "Provides the functionality to export PHP variables for visualization",
1288 | "homepage": "https://www.github.com/sebastianbergmann/exporter",
1289 | "keywords": [
1290 | "export",
1291 | "exporter"
1292 | ],
1293 | "support": {
1294 | "issues": "https://github.com/sebastianbergmann/exporter/issues",
1295 | "security": "https://github.com/sebastianbergmann/exporter/security/policy",
1296 | "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4"
1297 | },
1298 | "funding": [
1299 | {
1300 | "url": "https://github.com/sebastianbergmann",
1301 | "type": "github"
1302 | },
1303 | {
1304 | "url": "https://liberapay.com/sebastianbergmann",
1305 | "type": "liberapay"
1306 | },
1307 | {
1308 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1309 | "type": "thanks_dev"
1310 | },
1311 | {
1312 | "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
1313 | "type": "tidelift"
1314 | }
1315 | ],
1316 | "time": "2025-09-24T06:09:11+00:00"
1317 | },
1318 | {
1319 | "name": "sebastian/global-state",
1320 | "version": "6.0.2",
1321 | "source": {
1322 | "type": "git",
1323 | "url": "https://github.com/sebastianbergmann/global-state.git",
1324 | "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
1325 | },
1326 | "dist": {
1327 | "type": "zip",
1328 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
1329 | "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
1330 | "shasum": ""
1331 | },
1332 | "require": {
1333 | "php": ">=8.1",
1334 | "sebastian/object-reflector": "^3.0",
1335 | "sebastian/recursion-context": "^5.0"
1336 | },
1337 | "require-dev": {
1338 | "ext-dom": "*",
1339 | "phpunit/phpunit": "^10.0"
1340 | },
1341 | "type": "library",
1342 | "extra": {
1343 | "branch-alias": {
1344 | "dev-main": "6.0-dev"
1345 | }
1346 | },
1347 | "autoload": {
1348 | "classmap": [
1349 | "src/"
1350 | ]
1351 | },
1352 | "notification-url": "https://packagist.org/downloads/",
1353 | "license": [
1354 | "BSD-3-Clause"
1355 | ],
1356 | "authors": [
1357 | {
1358 | "name": "Sebastian Bergmann",
1359 | "email": "sebastian@phpunit.de"
1360 | }
1361 | ],
1362 | "description": "Snapshotting of global state",
1363 | "homepage": "https://www.github.com/sebastianbergmann/global-state",
1364 | "keywords": [
1365 | "global state"
1366 | ],
1367 | "support": {
1368 | "issues": "https://github.com/sebastianbergmann/global-state/issues",
1369 | "security": "https://github.com/sebastianbergmann/global-state/security/policy",
1370 | "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2"
1371 | },
1372 | "funding": [
1373 | {
1374 | "url": "https://github.com/sebastianbergmann",
1375 | "type": "github"
1376 | }
1377 | ],
1378 | "time": "2024-03-02T07:19:19+00:00"
1379 | },
1380 | {
1381 | "name": "sebastian/lines-of-code",
1382 | "version": "2.0.2",
1383 | "source": {
1384 | "type": "git",
1385 | "url": "https://github.com/sebastianbergmann/lines-of-code.git",
1386 | "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
1387 | },
1388 | "dist": {
1389 | "type": "zip",
1390 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
1391 | "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
1392 | "shasum": ""
1393 | },
1394 | "require": {
1395 | "nikic/php-parser": "^4.18 || ^5.0",
1396 | "php": ">=8.1"
1397 | },
1398 | "require-dev": {
1399 | "phpunit/phpunit": "^10.0"
1400 | },
1401 | "type": "library",
1402 | "extra": {
1403 | "branch-alias": {
1404 | "dev-main": "2.0-dev"
1405 | }
1406 | },
1407 | "autoload": {
1408 | "classmap": [
1409 | "src/"
1410 | ]
1411 | },
1412 | "notification-url": "https://packagist.org/downloads/",
1413 | "license": [
1414 | "BSD-3-Clause"
1415 | ],
1416 | "authors": [
1417 | {
1418 | "name": "Sebastian Bergmann",
1419 | "email": "sebastian@phpunit.de",
1420 | "role": "lead"
1421 | }
1422 | ],
1423 | "description": "Library for counting the lines of code in PHP source code",
1424 | "homepage": "https://github.com/sebastianbergmann/lines-of-code",
1425 | "support": {
1426 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
1427 | "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
1428 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
1429 | },
1430 | "funding": [
1431 | {
1432 | "url": "https://github.com/sebastianbergmann",
1433 | "type": "github"
1434 | }
1435 | ],
1436 | "time": "2023-12-21T08:38:20+00:00"
1437 | },
1438 | {
1439 | "name": "sebastian/object-enumerator",
1440 | "version": "5.0.0",
1441 | "source": {
1442 | "type": "git",
1443 | "url": "https://github.com/sebastianbergmann/object-enumerator.git",
1444 | "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906"
1445 | },
1446 | "dist": {
1447 | "type": "zip",
1448 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906",
1449 | "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906",
1450 | "shasum": ""
1451 | },
1452 | "require": {
1453 | "php": ">=8.1",
1454 | "sebastian/object-reflector": "^3.0",
1455 | "sebastian/recursion-context": "^5.0"
1456 | },
1457 | "require-dev": {
1458 | "phpunit/phpunit": "^10.0"
1459 | },
1460 | "type": "library",
1461 | "extra": {
1462 | "branch-alias": {
1463 | "dev-main": "5.0-dev"
1464 | }
1465 | },
1466 | "autoload": {
1467 | "classmap": [
1468 | "src/"
1469 | ]
1470 | },
1471 | "notification-url": "https://packagist.org/downloads/",
1472 | "license": [
1473 | "BSD-3-Clause"
1474 | ],
1475 | "authors": [
1476 | {
1477 | "name": "Sebastian Bergmann",
1478 | "email": "sebastian@phpunit.de"
1479 | }
1480 | ],
1481 | "description": "Traverses array structures and object graphs to enumerate all referenced objects",
1482 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
1483 | "support": {
1484 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
1485 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0"
1486 | },
1487 | "funding": [
1488 | {
1489 | "url": "https://github.com/sebastianbergmann",
1490 | "type": "github"
1491 | }
1492 | ],
1493 | "time": "2023-02-03T07:08:32+00:00"
1494 | },
1495 | {
1496 | "name": "sebastian/object-reflector",
1497 | "version": "3.0.0",
1498 | "source": {
1499 | "type": "git",
1500 | "url": "https://github.com/sebastianbergmann/object-reflector.git",
1501 | "reference": "24ed13d98130f0e7122df55d06c5c4942a577957"
1502 | },
1503 | "dist": {
1504 | "type": "zip",
1505 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957",
1506 | "reference": "24ed13d98130f0e7122df55d06c5c4942a577957",
1507 | "shasum": ""
1508 | },
1509 | "require": {
1510 | "php": ">=8.1"
1511 | },
1512 | "require-dev": {
1513 | "phpunit/phpunit": "^10.0"
1514 | },
1515 | "type": "library",
1516 | "extra": {
1517 | "branch-alias": {
1518 | "dev-main": "3.0-dev"
1519 | }
1520 | },
1521 | "autoload": {
1522 | "classmap": [
1523 | "src/"
1524 | ]
1525 | },
1526 | "notification-url": "https://packagist.org/downloads/",
1527 | "license": [
1528 | "BSD-3-Clause"
1529 | ],
1530 | "authors": [
1531 | {
1532 | "name": "Sebastian Bergmann",
1533 | "email": "sebastian@phpunit.de"
1534 | }
1535 | ],
1536 | "description": "Allows reflection of object attributes, including inherited and non-public ones",
1537 | "homepage": "https://github.com/sebastianbergmann/object-reflector/",
1538 | "support": {
1539 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
1540 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0"
1541 | },
1542 | "funding": [
1543 | {
1544 | "url": "https://github.com/sebastianbergmann",
1545 | "type": "github"
1546 | }
1547 | ],
1548 | "time": "2023-02-03T07:06:18+00:00"
1549 | },
1550 | {
1551 | "name": "sebastian/recursion-context",
1552 | "version": "5.0.1",
1553 | "source": {
1554 | "type": "git",
1555 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1556 | "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a"
1557 | },
1558 | "dist": {
1559 | "type": "zip",
1560 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a",
1561 | "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a",
1562 | "shasum": ""
1563 | },
1564 | "require": {
1565 | "php": ">=8.1"
1566 | },
1567 | "require-dev": {
1568 | "phpunit/phpunit": "^10.5"
1569 | },
1570 | "type": "library",
1571 | "extra": {
1572 | "branch-alias": {
1573 | "dev-main": "5.0-dev"
1574 | }
1575 | },
1576 | "autoload": {
1577 | "classmap": [
1578 | "src/"
1579 | ]
1580 | },
1581 | "notification-url": "https://packagist.org/downloads/",
1582 | "license": [
1583 | "BSD-3-Clause"
1584 | ],
1585 | "authors": [
1586 | {
1587 | "name": "Sebastian Bergmann",
1588 | "email": "sebastian@phpunit.de"
1589 | },
1590 | {
1591 | "name": "Jeff Welch",
1592 | "email": "whatthejeff@gmail.com"
1593 | },
1594 | {
1595 | "name": "Adam Harvey",
1596 | "email": "aharvey@php.net"
1597 | }
1598 | ],
1599 | "description": "Provides functionality to recursively process PHP variables",
1600 | "homepage": "https://github.com/sebastianbergmann/recursion-context",
1601 | "support": {
1602 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
1603 | "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
1604 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1"
1605 | },
1606 | "funding": [
1607 | {
1608 | "url": "https://github.com/sebastianbergmann",
1609 | "type": "github"
1610 | },
1611 | {
1612 | "url": "https://liberapay.com/sebastianbergmann",
1613 | "type": "liberapay"
1614 | },
1615 | {
1616 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1617 | "type": "thanks_dev"
1618 | },
1619 | {
1620 | "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
1621 | "type": "tidelift"
1622 | }
1623 | ],
1624 | "time": "2025-08-10T07:50:56+00:00"
1625 | },
1626 | {
1627 | "name": "sebastian/type",
1628 | "version": "4.0.0",
1629 | "source": {
1630 | "type": "git",
1631 | "url": "https://github.com/sebastianbergmann/type.git",
1632 | "reference": "462699a16464c3944eefc02ebdd77882bd3925bf"
1633 | },
1634 | "dist": {
1635 | "type": "zip",
1636 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf",
1637 | "reference": "462699a16464c3944eefc02ebdd77882bd3925bf",
1638 | "shasum": ""
1639 | },
1640 | "require": {
1641 | "php": ">=8.1"
1642 | },
1643 | "require-dev": {
1644 | "phpunit/phpunit": "^10.0"
1645 | },
1646 | "type": "library",
1647 | "extra": {
1648 | "branch-alias": {
1649 | "dev-main": "4.0-dev"
1650 | }
1651 | },
1652 | "autoload": {
1653 | "classmap": [
1654 | "src/"
1655 | ]
1656 | },
1657 | "notification-url": "https://packagist.org/downloads/",
1658 | "license": [
1659 | "BSD-3-Clause"
1660 | ],
1661 | "authors": [
1662 | {
1663 | "name": "Sebastian Bergmann",
1664 | "email": "sebastian@phpunit.de",
1665 | "role": "lead"
1666 | }
1667 | ],
1668 | "description": "Collection of value objects that represent the types of the PHP type system",
1669 | "homepage": "https://github.com/sebastianbergmann/type",
1670 | "support": {
1671 | "issues": "https://github.com/sebastianbergmann/type/issues",
1672 | "source": "https://github.com/sebastianbergmann/type/tree/4.0.0"
1673 | },
1674 | "funding": [
1675 | {
1676 | "url": "https://github.com/sebastianbergmann",
1677 | "type": "github"
1678 | }
1679 | ],
1680 | "time": "2023-02-03T07:10:45+00:00"
1681 | },
1682 | {
1683 | "name": "sebastian/version",
1684 | "version": "4.0.1",
1685 | "source": {
1686 | "type": "git",
1687 | "url": "https://github.com/sebastianbergmann/version.git",
1688 | "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17"
1689 | },
1690 | "dist": {
1691 | "type": "zip",
1692 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17",
1693 | "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17",
1694 | "shasum": ""
1695 | },
1696 | "require": {
1697 | "php": ">=8.1"
1698 | },
1699 | "type": "library",
1700 | "extra": {
1701 | "branch-alias": {
1702 | "dev-main": "4.0-dev"
1703 | }
1704 | },
1705 | "autoload": {
1706 | "classmap": [
1707 | "src/"
1708 | ]
1709 | },
1710 | "notification-url": "https://packagist.org/downloads/",
1711 | "license": [
1712 | "BSD-3-Clause"
1713 | ],
1714 | "authors": [
1715 | {
1716 | "name": "Sebastian Bergmann",
1717 | "email": "sebastian@phpunit.de",
1718 | "role": "lead"
1719 | }
1720 | ],
1721 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1722 | "homepage": "https://github.com/sebastianbergmann/version",
1723 | "support": {
1724 | "issues": "https://github.com/sebastianbergmann/version/issues",
1725 | "source": "https://github.com/sebastianbergmann/version/tree/4.0.1"
1726 | },
1727 | "funding": [
1728 | {
1729 | "url": "https://github.com/sebastianbergmann",
1730 | "type": "github"
1731 | }
1732 | ],
1733 | "time": "2023-02-07T11:34:05+00:00"
1734 | },
1735 | {
1736 | "name": "theseer/tokenizer",
1737 | "version": "1.2.3",
1738 | "source": {
1739 | "type": "git",
1740 | "url": "https://github.com/theseer/tokenizer.git",
1741 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
1742 | },
1743 | "dist": {
1744 | "type": "zip",
1745 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
1746 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
1747 | "shasum": ""
1748 | },
1749 | "require": {
1750 | "ext-dom": "*",
1751 | "ext-tokenizer": "*",
1752 | "ext-xmlwriter": "*",
1753 | "php": "^7.2 || ^8.0"
1754 | },
1755 | "type": "library",
1756 | "autoload": {
1757 | "classmap": [
1758 | "src/"
1759 | ]
1760 | },
1761 | "notification-url": "https://packagist.org/downloads/",
1762 | "license": [
1763 | "BSD-3-Clause"
1764 | ],
1765 | "authors": [
1766 | {
1767 | "name": "Arne Blankerts",
1768 | "email": "arne@blankerts.de",
1769 | "role": "Developer"
1770 | }
1771 | ],
1772 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
1773 | "support": {
1774 | "issues": "https://github.com/theseer/tokenizer/issues",
1775 | "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
1776 | },
1777 | "funding": [
1778 | {
1779 | "url": "https://github.com/theseer",
1780 | "type": "github"
1781 | }
1782 | ],
1783 | "time": "2024-03-03T12:36:25+00:00"
1784 | }
1785 | ],
1786 | "aliases": [],
1787 | "minimum-stability": "stable",
1788 | "stability-flags": {},
1789 | "prefer-stable": false,
1790 | "prefer-lowest": false,
1791 | "platform": {
1792 | "php": ">=8.1"
1793 | },
1794 | "platform-dev": {},
1795 | "plugin-api-version": "2.6.0"
1796 | }
1797 |
--------------------------------------------------------------------------------