getVersion() === 3; } public static function isMojangId(UuidInterface $uuid): bool { return $uuid->getVersion() === 4; } public static function registerRoutes(IRouter $router): void { $router->get('/uuid', fn($response, $request) => self::uuidResolver($response, $request)); $router->get('/blockedservers', fn($response, $request) => self::proxyBlockServers($response, $request)); // figure out how to proxy these someday to keep online mode working transparently $router->get('/session/minecraft/hasJoined', fn() => 501); $router->post('/session/minecraft/join', fn() => 501); } public static function uuidResolver(HttpResponseBuilder $response, HttpRequest $request): string { $response->setTypePlain(); $uuid = self::createOfflinePlayerUUID((string)$request->getParam('name')); return (string)match((string)$request->getParam('mode')) { 'str' => $uuid->toString(), 'urn' => $uuid->getUrn(), 'raw' => $uuid->getBytes(), 'int' => $uuid->getInteger(), default => $uuid->getHex(), }; } public static function getRequest(string $url, string $userAgent): object { $out = new stdClass; $curl = curl_init($url); curl_setopt_array($curl, [ CURLOPT_AUTOREFERER => true, CURLOPT_FAILONERROR => false, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TCP_NODELAY => true, CURLOPT_HEADER => true, CURLOPT_NOBODY => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_TCP_FASTOPEN => true, CURLOPT_CONNECTTIMEOUT => 2, CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_TIMEOUT => 5, CURLOPT_USERAGENT => $userAgent, ]); [$out->headers, $out->body] = explode("\r\n\r\n", curl_exec($curl)); curl_close($curl); $out->headers = explode("\r\n", $out->headers); $out->status = explode(' ', array_shift($out->headers), 3); return $out; } public static function sessionServerGetRequest(string $path, string $userAgent): object { return self::getRequest(self::SESSION_SERVER . $path, $userAgent); } public static function apiServerGetRequest(string $path, string $userAgent): object { return self::getRequest(self::API_SERVER . $path, $userAgent); } public static function getBlockedServersRaw(string $userAgent): object { return self::sessionServerGetRequest('/blockedservers', $userAgent); } public static function proxyBlockServers(HttpResponseBuilder $response, HttpRequest $request): string { $info = self::getBlockedServersRaw($request->getHeaderLine('User-Agent')); $response->setStatusCode((int)$info->status[1]); $response->setCacheControl('max-age=300'); foreach($info->headers as $header) { [$name, $value] = explode(':', $header); $name = strtolower(trim($name)); if(str_starts_with($name, 'x-') || $name === 'content-type') $response->setHeader($name, $value); } return $info->body; } public static function getMinecraftUUIDRaw(string $userName, string $userAgent): object { return self::apiServerGetRequest(sprintf('/users/profiles/minecraft/%s', $userName), $userAgent); } public static function getMinecraftUUID(string $userName, string $userAgent): ?object { $info = self::getMinecraftUUIDRaw($userName, $userAgent); if($info->status[1] !== '200') return null; return json_decode($info->body); } public static function getSessionMinecraftProfileRaw(string $uuid, string $userAgent): object { return self::sessionServerGetRequest(sprintf('/session/minecraft/profile/%s', $uuid), $userAgent); } public static function getSessionMinecraftProfile(string $uuid, string $userAgent): ?object { $info = self::getSessionMinecraftProfileRaw($uuid, $userAgent); if($info->status[1] !== '200') return null; return json_decode($info->body); } public static function proxySessionMinecraftProfile(HttpResponseBuilder $response, HttpRequest $request, string $uuid): string { $info = self::getSessionMinecraftProfileRaw($uuid, $request->getHeaderLine('User-Agent')); $response->setStatusCode((int)$info->status[1]); $response->setCacheControl('max-age=30'); foreach($info->headers as $header) { [$name, $value] = explode(':', $header); $name = strtolower(trim($name)); if(str_starts_with($name, 'x-') || $name === 'content-type') $response->setHeader($name, $value); } return $info->body; } }