Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions src/GameQ/Protocols/Hytaleone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php
/**
* This file is part of GameQ.
*
* GameQ is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GameQ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

namespace GameQ\Protocols;

use GameQ\Buffer;
use GameQ\Protocol;
use GameQ\Result;

/**
* HytaleONE Protocol Class
*
* @author H.Rouatbi <https://github.com/RouatbiH>
*/
class Hytaleone extends Protocol
{
/**
* Protocol Header
*/
const PACKET_HEADER = "HYREPLY\x00";

/**
* The protocol being used
*
* @var string
*/
protected $protocol = 'hytaleone';

/**
* String name of this protocol class
*
* @var string
*/
protected $name = 'hytaleone';

/**
* Longer string name of this protocol class
*
* @var string
*/
protected $name_long = "HytaleONE";

/**
* Array of packets we want to look up.
*
* @var array
*/
protected $packets = [
// There's no need for the basic packet because we have the full packet.
//self::PACKET_BASIC => "HYQUERY\x00\x00",
self::PACKET_ALL => "HYQUERY\x00\x01",
];

/**
* Normalize settings for this protocol
*
* @var array
*/
protected $normalize = [
// General
'general' => [
'hostname' => 'hostname',
'numplayers' => 'num_players',
'maxplayers' => 'max_players',
],
// Individual
'player' => [
'name' => 'name',
],
];

/**
* Process the response
*
* @return array
*/
public function processResponse()
{
$results = [];

foreach ($this->packets_response as $response) {
$buffer = new Buffer($response);

// Validate Magic
if ($buffer->read(8) !== self::PACKET_HEADER) {
continue;
}

$type = $buffer->readInt8();
$result = new Result();

// Basic Info
$result->add('hostname', $this->readString16($buffer));
$result->add('motd', $this->readString16($buffer));
$result->add('num_players', $buffer->readInt32());
$result->add('max_players', $buffer->readInt32());
$result->add('port', $buffer->readInt16());
$result->add('version', $this->readString16($buffer));
$result->add('protocol_version', $buffer->readInt32());
$result->add('protocol_hash', $this->readString16($buffer));

if ($type === 0x01) { // Full
// Players
$playerCount = $buffer->readInt32();
for ($i = 0; $i < $playerCount; $i++) {
$result->addPlayer('name', $this->readString16($buffer));
$result->addPlayer('uuid', $this->readUUID($buffer));
}

// Plugins
$pluginCount = $buffer->readInt32();
$plugins = [];
for ($i = 0; $i < $pluginCount; $i++) {
$plugins[] = [
'id' => $this->readString16($buffer),
'version' => $this->readString16($buffer),
'enabled' => $buffer->readInt8() !== 0,
];
}
$result->add('plugins', $plugins);
}

$results = array_merge($results, $result->fetch());
}

return $results;
}

/**
* Read a string with 2-byte length header
*
* @param Buffer $buffer
* @return string
*/
private function readString16(Buffer $buffer)
{
$length = $buffer->readInt16();
return $buffer->read($length);
}

/**
* Read UUID (Big Endian 128-bit integer rendered as hex string)
*
* @param Buffer $buffer
* @return string
*/
private function readUUID(Buffer $buffer)
{
// MSB (8 bytes)
$msb = unpack('J', $buffer->read(8))[1];
// LSB (8 bytes)
$lsb = unpack('J', $buffer->read(8))[1];

// Convert to hex
$msbHex = sprintf('%016x', $msb);
$lsbHex = sprintf('%016x', $lsb);

$hex = $msbHex . $lsbHex;

// 8-4-4-4-12 format
return sprintf(
'%s-%s-%s-%s-%s',
substr($hex, 0, 8),
substr($hex, 8, 4),
substr($hex, 12, 4),
substr($hex, 16, 4),
substr($hex, 20)
);
}
}
49 changes: 49 additions & 0 deletions tests/Protocols/Hytaleone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* This file is part of GameQ.
*
* GameQ is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GameQ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

namespace GameQ\Tests\Protocols;

/**
* Test Class for HytaleONE
*
* @package GameQ\Tests\Protocols
*/
class Hytaleone extends Base
{
/**
* Test responses for HytaleONE
*
* @dataProvider loadData
*
* @param $responses
* @param $result
*/
public function testResponses($responses, $result)
{
// Pull the first key off the array this is the server ip:port
$server = key($result);

$testResult = $this->queryTest(
$server,
'hytaleone',
$responses
);

$this->assertEquals($result[$server], $testResult);
}
}
Binary file not shown.
1 change: 1 addition & 0 deletions tests/Protocols/Providers/Hytaleone/1_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"193.233.248.212:5520":{"gq_address":"193.233.248.212","gq_joinlink":null,"gq_name":"HytaleONE","gq_online":true,"gq_port_client":5520,"gq_port_query":5520,"gq_protocol":"hytaleone","gq_transport":"udp","gq_type":"hytaleone","hostname":"Hytale Server","max_players":1000,"motd":"Лучший сервер в мире! Первый в России!","num_players":33,"players":[{"name":"ICMEPTb","uuid":"954024d2-03ec-4040-b3e7-9c16dede8d50"},{"name":"Space_Taurus","uuid":"53338768-113c-4162-b409-a737fa1827e2"},{"name":"BROSKO","uuid":"f8bf608d-9aca-49b9-ab85-fa74200935fe"},{"name":"Islam7878","uuid":"0a95d58c-3410-4fef-8f82-2ce10403c10e"},{"name":"FleetMesa157","uuid":"f8aabd97-9c9f-4627-b32b-5eaf5af3027b"},{"name":"Arifur","uuid":"19163e70-4889-4a5a-9120-115d300a0dcf"},{"name":"lisi4ka","uuid":"e9d8214c-57c1-4e2a-a41e-ce34326e9c6a"},{"name":"__Cookie__","uuid":"575f61aa-eca1-430d-a445-c0c72f0aea43"},{"name":"Jollylaps","uuid":"632cc467-e2db-44dd-971f-7ce8a15c18f5"},{"name":"bliznyashka","uuid":"6136c9ce-4e67-4f91-a420-1d78fb1605d7"},{"name":"Fei_Long","uuid":"f5f955dc-859b-43b1-976f-846fb311f889"},{"name":"N41X","uuid":"85848610-d186-4d68-90b1-bd0a526f6b74"},{"name":"Ashraa","uuid":"e906f07a-6a77-4b2a-a2b5-5d7c8d278593"},{"name":"ResoluteCliff797","uuid":"757801d8-7b30-43e7-baf5-d7dd2989e048"},{"name":"Mariska","uuid":"d488b6b5-f386-4717-afdb-8b5397798abd"},{"name":"TokEiMan","uuid":"7dc727bf-b921-4c76-9bc1-a5dc7ece4058"},{"name":"daniFX","uuid":"ad1a17e9-7a79-43b6-badf-653f106ee217"},{"name":"credo","uuid":"6e79a26b-ac73-419d-a86b-9816d5cdbb65"},{"name":"SwiftMoose374","uuid":"0a8234e7-36a2-4093-8fae-b8edeefa5246"},{"name":"Mandarinka","uuid":"24dbd120-25ec-4b80-a17a-be8664219ce6"},{"name":"3xnrnt","uuid":"8429b34d-7968-43b9-8934-cf0a4edb2b7f"},{"name":"wowikmur","uuid":"40e078a7-2a19-44d8-a100-b2c655b90216"},{"name":"DanyaPro405","uuid":"71367492-b8e0-4d65-afb6-be975d4cc13d"},{"name":"AlexR","uuid":"7a7bd1a2-565e-47b5-b1fb-bedc0d557eb6"},{"name":"_CAXAPOK_","uuid":"5690d909-57c5-473a-a170-a0cef5bde153"},{"name":"1nkvi","uuid":"9ef4f3d1-a817-487d-b63f-73134fdb03c0"},{"name":"im_chert42","uuid":"ae6030ee-a07c-4512-a68d-f3a7bac9dcf8"},{"name":"GG_and","uuid":"b21fff04-d94c-4041-b60b-443f61cc1552"},{"name":"_Les_","uuid":"44c435ea-61d2-4d37-946e-170a569d430f"},{"name":"Pelvit","uuid":"a81cbdd0-6516-4a52-bbd6-7b32bb3a75ab"},{"name":"Popochka","uuid":"8d09c737-37b0-4e48-9438-32dd34a71d79"},{"name":"NaturalTrail387","uuid":"9c030313-e397-476e-b084-c44a238e7597"},{"name":"Marlya","uuid":"d9d9a310-8b77-4791-9dcc-4ee2571a4424"}],"plugins":[{"id":"Hytale:WorldLocationCondition","version":"1.0.0","enabled":true},{"id":"Hytale:ServerManager","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:TimeModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:CollisionModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:DebugPlugin","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:SplitVelocity","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:UpdateModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:AccessControlModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:ItemModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:Deployables","version":"1.0.0","enabled":true},{"id":"Hytale:BlockSetModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:SprintForce","version":"1.0.0","enabled":true},{"id":"Hytale:MigrationModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:AssetModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:ConsoleModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:Model","version":"1.0.0","enabled":true},{"id":"Hytale:CrouchSlide","version":"1.0.0","enabled":true},{"id":"Hytale:Universe","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:TagSet","version":"1.0.0","enabled":true},{"id":"Hytale:CommandMacro","version":"1.0.0","enabled":true},{"id":"Hytale:SafetyRoll","version":"1.0.0","enabled":true},{"id":"Hytale:HytaleGenerator","version":"1.0.0","enabled":true},{"id":"Hytale:CommonAssetModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:CosmeticsModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:LegacyModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:LANDiscovery","version":"1.0.0","enabled":true},{"id":"Hytale:Mantling","version":"1.0.0","enabled":true},{"id":"Hytale:PermissionsModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:EntityModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:BlockTick","version":"1.0.0","enabled":true},{"id":"Hytale:InteractionModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:ServerPlayerListModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:Parkour","version":"1.0.0","enabled":true},{"id":"Hytale:Ambience","version":"1.0.0","enabled":true},{"id":"Hytale:I18nModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:Shop","version":"1.0.0","enabled":true},{"id":"Hytale:FlyCameraModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:BlockHealthModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:ConnectedBlocksModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:BlockTypeModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:BlockModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:Weather","version":"1.0.0","enabled":true},{"id":"Hytale:Reputation","version":"1.0.0","enabled":true},{"id":"Hytale:ProjectileModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:WorldGen","version":"1.0.0","enabled":true},{"id":"Hytale:BlockStateModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:AssetEditor","version":"1.0.0","enabled":true},{"id":"Hytale:BuilderTools","version":"1.0.0","enabled":true},{"id":"Hytale:Fluid","version":"1.0.0","enabled":true},{"id":"Hytale:Crafting","version":"1.0.0","enabled":true},{"id":"Hytale:SingleplayerModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:EntityStatsModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:Objectives","version":"1.0.0","enabled":true},{"id":"Hytale:Path","version":"1.0.0","enabled":true},{"id":"Hytale:Teleport","version":"1.0.0","enabled":true},{"id":"Hytale:EntityUIModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:Stash","version":"1.0.0","enabled":true},{"id":"Hytale:StaminaModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:BlockPhysics","version":"1.0.0","enabled":true},{"id":"Hytale:ShopReputation","version":"1.0.0","enabled":true},{"id":"Hytale:DamageModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:PrefabSpawnerModule","version":"2026.1.24-6e2d4fc36","enabled":true},{"id":"Hytale:ObjectiveShop","version":"1.0.0","enabled":true},{"id":"Hytale:Teleporter","version":"1.0.0","enabled":true},{"id":"Hytale:Farming","version":"1.0.0","enabled":true},{"id":"Hytale:ObjectiveReputation","version":"1.0.0","enabled":true},{"id":"Hytale:BlockSpawner","version":"1.0.0","enabled":true},{"id":"Hytale:Instances","version":"1.0.0","enabled":true},{"id":"Hytale:Camera","version":"1.0.0","enabled":true},{"id":"Hytale:NPC","version":"1.0.0","enabled":true},{"id":"Hytale:Mounts","version":"1.0.0","enabled":true},{"id":"Hytale:NPCEditor","version":"1.0.0","enabled":true},{"id":"Hytale:Memories","version":"1.0.0","enabled":true},{"id":"Hytale:NPCShop","version":"1.0.0","enabled":true},{"id":"Hytale:NPCCombatActionEvaluator","version":"1.0.0","enabled":true},{"id":"Hytale:CreativeHub","version":"1.0.0","enabled":true},{"id":"Hytale:Spawning","version":"1.0.0","enabled":true},{"id":"Hytale:NPCObjectives","version":"1.0.0","enabled":true},{"id":"Hytale:Flock","version":"1.0.0","enabled":true},{"id":"Hytale:Beds","version":"1.0.0","enabled":true},{"id":"Hytale:Portals","version":"1.0.0","enabled":true},{"id":"Hytale:NPCReputation","version":"1.0.0","enabled":true},{"id":"ehko:Hylograms","version":"1.0.0","enabled":true},{"id":"tins:HyAnnouncer","version":"1.1.5","enabled":true},{"id":"Economy:EconomySystem","version":"1.0.0","enabled":true},{"id":"com.nhulston:Essentials","version":"1.5.2","enabled":true},{"id":"Buuz135:MultipleHUD","version":"1.0.3","enabled":true},{"id":"Playtime:Playtime","version":"1.2.0","enabled":true},{"id":"Shiirroo:TPS","version":"1.0.4","enabled":true},{"id":":NPC Dialog","version":"1.1.3","enabled":true},{"id":"BlameJared:SimplyTrash","version":"1.0.0","enabled":true},{"id":"CORE:CoreProtect","version":"1.3.0","enabled":true},{"id":"HytaleOne:Query","version":"1.1.0","enabled":true},{"id":"jamo:serverfixes","version":"1.1.2","enabled":true},{"id":"Buuz135:SimpleClaims","version":"1.0.16","enabled":true},{"id":"LuckPerms:LuckPerms","version":"5.5.24","enabled":true},{"id":"Devify:ProximityCore","version":"1.1.4","enabled":true},{"id":"leniad:TextSigns","version":"1.0.2","enabled":true},{"id":"Zuxaw:RPGLeveling","version":"0.1.8","enabled":true}],"port":5520,"protocol_hash":"6708f121966c1c443f4b0eb525b2f81d0a8dc61f5003a692a8fa157e5e02cea9","protocol_version":1,"version":"2026.01.24-6e2d4fc36"}}
Binary file not shown.
Loading