-- * bin/client/zagan-bet-client---.exe * bin/server/zagan-bet-server--- * * is MAJOR.MINOR.PATCH, is linux|windows|macos and * is x86_64|i686|aarch64. Example: * * bin/client/zagan-bet-client-3.1.1-linux-x86_64 * bin/server/zagan-bet-server-3.1.1-linux-x86_64 * * The hashes are computed per request with hash_file(). That is a few tens of * milliseconds for a handful of builds; add a cache keyed on mtime if the * directory ever grows. */ const KINDS = [ 'client' => [ 'title' => 'Client', 'hint' => 'Needs a terminal of at least 80×24 cells.', ], 'server' => [ 'title' => 'Server', 'hint' => 'Generates its own TLS identity on first run.', ], ]; const PLATFORMS = [ 'linux' => 'Linux', 'windows' => 'Windows', 'macos' => 'macOS', ]; const ARCHES = [ 'x86_64' => '64-bit', 'i686' => '32-bit', 'aarch64' => 'ARM64', ]; // Newest version first, then 64-bit builds before 32-bit. const PLATFORM_ORDER = ['linux' => 0, 'windows' => 1, 'macos' => 2]; const ARCH_ORDER = ['x86_64' => 0, 'aarch64' => 1, 'i686' => 2]; function e(?string $value): string { return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function human_size(int $bytes): string { if ($bytes >= 1048576) { return number_format($bytes / 1048576, 1) . ' MB'; } if ($bytes >= 1024) { return number_format($bytes / 1024) . ' KB'; } return $bytes . ' B'; } /** * @return array{version:string,platform:string,arch:string,file:string,size:int,mtime:int,sha256:string}|null */ function read_release(string $kind, string $directory, string $entry): ?array { $pattern = '/^zagan-bet-' . preg_quote($kind, '/') . '-v?(\d+\.\d+\.\d+)-([a-z]+)-([a-z0-9_]+)(\.exe)?$/i'; if (preg_match($pattern, $entry, $matches) !== 1) { return null; } $realDirectory = realpath($directory); $real = realpath($directory . '/' . $entry); if ($realDirectory === false || $real === false) { return null; } // Never serve anything that resolves outside the published directory. if (!str_starts_with($real, $realDirectory . DIRECTORY_SEPARATOR)) { return null; } if (!is_file($real)) { return null; } $size = filesize($real); $hash = hash_file('sha256', $real); if ($size === false || $hash === false) { return null; } return [ 'version' => $matches[1], 'platform' => strtolower($matches[2]), 'arch' => strtolower($matches[3]), 'file' => $entry, 'size' => $size, 'mtime' => filemtime($real) ?: 0, 'sha256' => $hash, ]; } $base = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/')), '/'); if ($base === '.' || $base === '/') { $base = ''; } $releases = []; $newest = 0; foreach (KINDS as $kind => $meta) { $found = []; $directory = __DIR__ . '/bin/' . $kind; if (is_dir($directory)) { $entries = scandir($directory); if ($entries !== false) { foreach ($entries as $entry) { if ($entry === '.' || $entry === '..') { continue; } $release = read_release($kind, $directory, $entry); if ($release === null) { continue; } $release['href'] = $base . '/bin/' . $kind . '/' . rawurlencode($release['file']); $found[] = $release; $newest = max($newest, $release['mtime']); } } } usort($found, static function (array $a, array $b): int { $byVersion = version_compare($b['version'], $a['version']); if ($byVersion !== 0) { return $byVersion; } $byPlatform = (PLATFORM_ORDER[$a['platform']] ?? 9) <=> (PLATFORM_ORDER[$b['platform']] ?? 9); if ($byPlatform !== 0) { return $byPlatform; } $byArch = (ARCH_ORDER[$a['arch']] ?? 9) <=> (ARCH_ORDER[$b['arch']] ?? 9); if ($byArch !== 0) { return $byArch; } return strcmp($a['file'], $b['file']); }); $releases[$kind] = $found; } ?> Download · zagan.bet

zagan.bet

Terminal betting. Slots, roulette, blackjack and crash.

0): ?>

Builds published UTC

$meta): ?>

Nothing published yet.

Checksums

SHA-256 of every published file.

File Version Size SHA-256