0, 'path' => '/intern/statistik/', 'secure' => true, 'httponly' => true, 'samesite' => 'Strict', ]); session_start(); $configPath = __DIR__ . '/stats-config.local.php'; if (!is_file($configPath)) { http_response_code(503); exit('Die interne Statistik ist noch nicht konfiguriert.'); } $config = require $configPath; if (isset($_GET['logout'])) { $_SESSION = []; session_destroy(); header('Location: ./'); exit; } $loginError = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['username'], $_POST['password'])) { $userOk = hash_equals((string) $config['username'], (string) $_POST['username']); $passwordOk = hash_equals((string) $config['password_sha256'], hash('sha256', (string) $_POST['password'])); if ($userOk && $passwordOk) { session_regenerate_id(true); $_SESSION['authenticated'] = true; $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); header('Location: ./'); exit; } usleep(350000); $loginError = 'Anmeldung fehlgeschlagen.'; } function fetchJson(string $url, string $method = 'GET', ?string $body = null): ?array { if (function_exists('curl_init')) { $handle = curl_init($url); $options = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_CONNECTTIMEOUT => 4, CURLOPT_TIMEOUT => 8, CURLOPT_USERAGENT => 'GerGames-Internal-Stats/1.0', CURLOPT_HTTPHEADER => ['Accept: application/json', 'Content-Type: application/json'], CURLOPT_CUSTOMREQUEST => $method, ]; if ($body !== null) { $options[CURLOPT_POSTFIELDS] = $body; } curl_setopt_array($handle, $options); $response = curl_exec($handle); $status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE); curl_close($handle); if (!is_string($response) || $status < 200 || $status >= 300) return null; } else { $context = stream_context_create(['http' => [ 'method' => $method, 'timeout' => 8, 'ignore_errors' => true, 'header' => "Accept: application/json\r\nContent-Type: application/json\r\nUser-Agent: GerGames-Internal-Stats/1.0\r\n", 'content' => $body ?? '', ]]); $response = @file_get_contents($url, false, $context); if (!is_string($response)) return null; } $decoded = json_decode($response, true); return is_array($decoded) ? $decoded : null; } function e(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } if (empty($_SESSION['authenticated'])): ?> GerGames – Interne Statistik

Interne Statistik

Geschützter GerGames-Bereich

50000) { $promoError = 'Die Anzahl der Einlösungen muss zwischen 1 und 50.000 liegen.'; } elseif ($noteLength > 160) { $promoError = 'Die Bemerkung darf höchstens 160 Zeichen lang sein.'; } elseif ($httpKey === '' || !is_string($rpcBase) || $rpcBase === (string) ($config['nakama_rpc_url'] ?? '')) { $promoError = 'Die interne Codeverwaltung ist noch nicht mit Nakama verbunden.'; } else { $url = $rpcBase . '?http_key=' . rawurlencode($httpKey) . '&unwrap'; $response = fetchJson($url, 'POST', json_encode(['reward' => $reward, 'max_uses' => $maxUses, 'note' => $note], JSON_THROW_ON_ERROR)); if (is_array($response) && isset($response['payload']) && is_string($response['payload'])) { $decodedPayload = json_decode($response['payload'], true); if (is_array($decodedPayload)) $response = $decodedPayload; } if (is_array($response) && !empty($response['accepted']) && !empty($response['code'])) { $_SESSION['promo_flash'] = [ 'code' => (string) $response['code'], 'reward' => (string) ($response['reward'] ?? $reward), 'max_uses' => (int) ($response['max_uses'] ?? $maxUses), 'note' => (string) ($response['note'] ?? $note), ]; header('Location: ./'); exit; } $promoError = 'Der Server konnte keinen Code erstellen. Prüfe die Verbindung und versuche es erneut.'; } } } $releases = fetchJson((string) $config['gitea_releases_api']) ?? []; $downloadTotal = 0; $latestDownloads = 0; $latestRelease = 'Noch kein Release'; $platformDownloads = ['windows' => 0, 'android' => 0]; foreach ($releases as $releaseIndex => $release) { if (!is_array($release)) continue; if ($releaseIndex === 0) $latestRelease = (string) ($release['name'] ?? $release['tag_name'] ?? 'Aktuelles Release'); foreach (($release['assets'] ?? []) as $asset) { if (!is_array($asset)) continue; $count = max(0, (int) ($asset['download_count'] ?? 0)); $downloadTotal += $count; if ($releaseIndex === 0) $latestDownloads += $count; $name = strtolower((string) ($asset['name'] ?? '')); if (substr($name, -4) === '.exe') $platformDownloads['windows'] += $count; if (substr($name, -4) === '.apk') $platformDownloads['android'] += $count; } } $playerStats = null; $httpKey = trim((string) ($config['nakama_http_key'] ?? '')); if ($httpKey !== '') { $statsUrl = (string) $config['nakama_rpc_url'] . '?http_key=' . rawurlencode($httpKey) . '&unwrap'; $rpcResponse = fetchJson($statsUrl, 'POST', '{}'); if (is_array($rpcResponse)) { $playerStats = $rpcResponse; if (isset($rpcResponse['payload']) && is_string($rpcResponse['payload'])) { $payload = json_decode($rpcResponse['payload'], true); if (is_array($payload)) $playerStats = $payload; } } } $players = (int) ($playerStats['total_players'] ?? 0); $today = gmdate('Y-m-d'); $activeToday = (int) (($playerStats['daily_active'] ?? [])[$today] ?? 0); $updatedAt = isset($playerStats['updated_at']) ? date('d.m.Y H:i', intdiv((int) $playerStats['updated_at'], 1000)) : 'Noch keine Daten'; $promoCodes = null; $promoListError = ''; if ($httpKey !== '') { $listRpcUrl = preg_replace('~/private_stats$~', '/list_unlock_codes', (string) ($config['nakama_rpc_url'] ?? '')); if (is_string($listRpcUrl) && $listRpcUrl !== (string) ($config['nakama_rpc_url'] ?? '')) { $listUrl = $listRpcUrl . '?http_key=' . rawurlencode($httpKey) . '&unwrap'; $listResponse = fetchJson($listUrl, 'POST', '{}'); if (is_array($listResponse) && isset($listResponse['payload']) && is_string($listResponse['payload'])) { $decodedPayload = json_decode($listResponse['payload'], true); if (is_array($decodedPayload)) $listResponse = $decodedPayload; } if (is_array($listResponse) && isset($listResponse['codes']) && is_array($listResponse['codes'])) { $promoCodes = $listResponse['codes']; } else { $promoListError = 'Die Code-Übersicht konnte gerade nicht geladen werden.'; } } else { $promoListError = 'Die interne Codeverwaltung ist noch nicht mit Nakama verbunden.'; } } else { $promoListError = 'Die interne Codeverwaltung ist noch nicht mit Nakama verbunden.'; } ?> Void Bastion – Interne Statistik

GERGAMES · NICHT ÖFFENTLICH

Void Bastion Statistik

Abmelden
Downloads insgesamt
Downloads aktuelles Release
Registrierte Piloten
Heute aktive Piloten
Windows-Downloads
Android-Downloads

Die Downloadwerte stammen direkt aus den Gitea-Release-Anhängen und werden alle fünf Minuten neu geladen.

Die private Nakama-Statistik ist noch nicht mit dem HTTP-Schlüssel verbunden.

Letzte Spieleraktualisierung:

Diese Seite ist nicht verlinkt, von Suchmaschinen ausgeschlossen und verlangt eine Anmeldung. Es werden ausschließlich zusammengefasste Werte dargestellt.

Freischaltcodes erstellen

Codes und Bemerkungen werden ausschließlich in der geschützten Serverablage verwaltet. Der Klartext ist nur in diesem angemeldeten Bereich sichtbar.

Code erstellt: · offen · maximal Einlösungen ·

Code-Übersicht

Noch keine Freischaltcodes vorhanden.

= $maxUses ? 'redeemed' : ($uses > 0 ? 'partial' : 'open'); $statusLabel = $status === 'redeemed' ? 'Eingelöst' : ($status === 'partial' ? 'Teilweise eingelöst' : 'Offen'); $createdAt = (int) ($promoCode['created_at'] ?? 0); $createdLabel = $createdAt > 0 ? date('d.m.Y H:i', intdiv($createdAt, 1000)) : '—'; $codeLabel = (string) ($promoCode['code'] ?? ''); if ($codeLabel === '') $codeLabel = 'Alter Code – Klartext nicht gespeichert'; ?>
CodeFreischaltungStatusEinlösungenErstelltBemerkung
/