cache['emulator_jar_path'] ??= $this->doDetectEmulatorJarPath(); } private function doDetectEmulatorJarPath(): string { // 1. Check setting $setting = $this->getSetting('emulator_jar_path'); if ($setting && $this->exists($setting)) { return $setting; } // 2. Check well-known directories $dirNames = ['Emulator', 'emulator', 'emulator-source', 'arcturus', 'morningstar', 'hotel', 'habbo-emulator', 'nitro-emulator']; $found = $this->smartSearch($dirNames, ['*.jar'], ['/var/www', '/opt', '/home', '/root', '/srv', base_path()]); if ($found) { return $found; } // 3. Find any directory containing a JAR $result = Process::timeout(10)->run( 'find /var/www /opt /home /root /srv -maxdepth 4 -name "*.jar" -type f 2>/dev/null | head -1', ); if ($result->successful()) { $jarFile = trim($result->output()); if ($jarFile !== '' && str_ends_with($jarFile, '.jar')) { return dirname($jarFile); } } return '/root/emulator'; } public function detectEmulatorSourcePath(): string { return $this->cache['emulator_source_path'] ??= $this->doDetectEmulatorSourcePath(); } private function doDetectEmulatorSourcePath(): string { $setting = $this->getSetting('emulator_source_path'); if ($setting && $this->exists($setting)) { return $setting; } $dirNames = ['emulator-source', 'emulator', 'arcturus-source', 'morningstar-source', 'Arcturus-Emulator', 'Morningstar', 'hotel-source']; $found = $this->smartSearch($dirNames, ['pom.xml', 'build.gradle', 'src'], ['/var/www', '/opt', '/home', '/root', '/srv', base_path()]); if ($found) { return $found; } return '/var/www/emulator-source'; } public function detectEmulatorServiceName(): string { return $this->cache['emulator_service'] ??= $this->doDetectEmulatorServiceName(); } private function doDetectEmulatorServiceName(): string { $setting = $this->getSetting('emulator_service_name'); if ($setting && $setting !== '' && $setting !== '0') { // Verify it actually exists $check = Process::timeout(5)->run( 'systemctl list-unit-files ' . escapeshellarg($setting) . '.service 2>/dev/null | grep -q ' . escapeshellarg($setting) . ' && echo found', ); if ($check->successful() && trim($check->output()) === 'found') { return $setting; } } // Try common service names $possibleServices = [ 'emulator', 'arcturus', 'morningstar', 'arcturus-emulator', 'habbo', 'hotel', 'game', 'nitro-emulator', 'hotel-server', 'arcturus-emu', 'morningstar-emu', ]; foreach ($possibleServices as $service) { $check = Process::timeout(5)->run( 'systemctl list-unit-files ' . escapeshellarg($service) . '.service 2>/dev/null | grep -q ' . escapeshellarg($service) . ' && echo found', ); if ($check->successful() && trim($check->output()) === 'found') { return $service; } } // Also check running services $running = Process::timeout(5)->run('systemctl list-units --type=service --state=running --no-legend 2>/dev/null'); if ($running->successful()) { $lines = explode("\n", $running->output()); foreach ($lines as $line) { foreach ($possibleServices as $service) { if (str_contains($line, $service)) { return $service; } } } } // Check for any Java process that looks like an emulator $javaProc = Process::timeout(5)->run("ps aux | grep -i 'java.*jar\\|arcturus\\|morningstar\\|emulator' | grep -v grep | head -1"); if ($javaProc->successful() && trim($javaProc->output()) !== '') { $output = trim($javaProc->output()); // Try to extract service name from systemd if (preg_match('/systemd.*--name[=\s]+(\S+)/', $output, $matches)) { return $matches[1]; } } return 'emulator'; } public function detectEmulatorJavaProcess(): ?array { $result = Process::timeout(5)->run( "ps aux | grep -i '[j]ava.*jar' | head -1", ); if (! $result->successful() || trim($result->output()) === '') { return null; } $line = trim($result->output()); $parts = preg_split('/\s+/', $line); return [ 'pid' => $parts[1] ?? null, 'full_command' => $line, ]; } // ─── Nitro Client / Renderer ─────────────────────────────── public function detectNitroClientPath(): string { return $this->cache['nitro_client_path'] ??= $this->doDetectNitroClientPath(); } private function doDetectNitroClientPath(): string { $setting = $this->getSetting('nitro_client_path'); if ($setting && $this->exists($setting)) { return $setting; } $dirNames = ['nitro-client', 'nitro-client-v3', 'nitro-V3', 'nitro-V3-client', 'Nitro-V3', 'Nitro_Client', 'nitro', 'client']; $found = $this->smartSearch($dirNames, ['package.json', 'src'], ['/var/www', '/opt', '/home', '/root', base_path(), base_path() . '/..']); if ($found) { return $found; } return base_path() . '/nitro-client'; } public function detectNitroRendererPath(): string { return $this->cache['nitro_renderer_path'] ??= $this->doDetectNitroRendererPath(); } private function doDetectNitroRendererPath(): string { $setting = $this->getSetting('nitro_renderer_path'); if ($setting && $this->exists($setting)) { return $setting; } $dirNames = ['nitro-renderer', 'nitro-render-v3', 'Nitro_Render_V3', 'Nitro-Render-V3', 'nitro-render', 'renderer']; $found = $this->smartSearch($dirNames, ['package.json', 'src'], ['/var/www', '/opt', '/home', '/root', base_path(), base_path() . '/..']); if ($found) { return $found; } return base_path() . '/nitro-renderer'; } public function detectNitroWebroot(): string { return $this->cache['nitro_webroot'] ??= $this->doDetectNitroWebroot(); } private function doDetectNitroWebroot(): string { $setting = $this->getSetting('nitro_webroot'); if ($setting && $this->exists($setting)) { return $setting; } // Check nginx config for root directives $nginxRoot = $this->detectNginxWebroot(); if ($nginxRoot) { return $nginxRoot; } $possiblePaths = [ '/var/www/Client', '/var/www/client', '/var/www/Nitro', '/var/www/nitro', '/var/www/html/Client', '/var/www/html/client', '/var/www/atomcms/public/Client', '/var/www/atomcms/public/nitro', ]; foreach ($possiblePaths as $path) { if ($this->exists($path)) { return $path; } } return '/var/www/Client'; } public function detectNitroBuildPath(): string { return $this->cache['nitro_build_path'] ??= $this->doDetectNitroBuildPath(); } private function doDetectNitroBuildPath(): string { $setting = $this->getSetting('nitro_build_path'); if ($setting && $this->exists($setting)) { return $setting; } $clientPath = $this->detectNitroClientPath(); $distPath = $clientPath . '/dist'; if ($this->exists($distPath)) { return $distPath; } $possiblePaths = [ '/var/www/nitro-client/dist', '/var/www/nitro-V3/dist', '/var/www/atomcms/nitro-client/dist', ]; foreach ($possiblePaths as $path) { if ($this->exists($path)) { return $path; } } return $clientPath . '/dist'; } // ─── Gamedata ────────────────────────────────────────────── public function detectGamedataPath(): string { return $this->cache['gamedata_path'] ??= $this->doDetectGamedataPath(); } private function doDetectGamedataPath(): string { // Always scan first to detect actual case on disk $result = Process::timeout(5)->run('ls -1 /var/www/ 2>/dev/null | grep -i "^gamedata$"'); if ($result->successful() && trim($result->output()) !== '') { $actualDir = trim($result->output()); $path = '/var/www/' . $actualDir; if ($this->exists($path)) { // Save detected path to setting for future use $this->saveSetting('gamedata_path', $path); return $path; } } // Fallback to setting if scan didn't find it $setting = $this->getSetting('gamedata_path'); if ($setting && $this->exists($setting)) { return $setting; } // Last resort: check common paths $possiblePaths = [ '/var/www/atomcms/public/gamedata', '/var/www/html/gamedata', '/opt/gamedata', '/root/gamedata', ]; foreach ($possiblePaths as $path) { if ($this->exists($path)) { return $path; } } return '/var/www/gamedata'; } public function detectFurnitureIconsPath(): string { $setting = $this->getSetting('furniture_icons_path'); if ($setting && $this->exists($setting)) { return $setting; } $gamedata = $this->detectGamedataPath(); $iconsPath = $gamedata . '/icons'; if ($this->exists($iconsPath)) { return $iconsPath; } return '/var/www/Gamedata/icons'; } public function detectCatalogIconsPath(): string { $setting = $this->getSetting('catalog_icons_path'); if ($setting && $this->exists($setting)) { return $setting; } $gamedata = $this->detectGamedataPath(); $cataloguePath = $gamedata . '/catalogue'; if ($this->exists($cataloguePath)) { return $cataloguePath; } return '/var/www/Gamedata/catalogue'; } // ─── Nginx ───────────────────────────────────────────────── public function detectNginxConfigPath(): ?string { $possibleConfigs = [ '/etc/nginx/sites-enabled/cms.conf', '/etc/nginx/sites-enabled/atom.conf', '/etc/nginx/sites-enabled/default', '/etc/nginx/conf.d/atom.conf', '/etc/nginx/conf.d/cms.conf', '/etc/nginx/conf.d/default.conf', ]; foreach ($possibleConfigs as $config) { if (file_exists($config)) { return $config; } } return null; } public function detectNginxWebroot(): ?string { $configPath = $this->detectNginxConfigPath(); if (! $configPath) { return null; } $content = @file_get_contents($configPath); if (! $content) { return null; } // Look for root directives that point to Client/nitro directories if (preg_match_all('/^\s*root\s+(.+);/m', $content, $matches)) { foreach ($matches[1] as $root) { $root = trim($root); if ((str_contains(strtolower($root), 'client') || str_contains(strtolower($root), 'nitro')) && $this->exists($root)) { return $root; } } // Return first valid root foreach ($matches[1] as $root) { $root = trim($root); if ($this->exists($root)) { return $root; } } } return null; } public function detectGamedataUrlPath(): ?string { $configPath = $this->detectNginxConfigPath(); if (! $configPath) { return null; } $content = @file_get_contents($configPath); if (! $content) { return null; } // Look for location /gamedata { alias /var/www/Gamedata; } // Return the location path (the URL path), not the alias path if (preg_match('/location\s+(\/gamedata)\s*\{/i', $content, $matches)) { return $matches[1]; // Returns /gamedata } // Fallback: check if there's a root that includes gamedata if (preg_match_all('/^\s*root\s+(.+);/m', $content, $matches)) { foreach ($matches[1] as $root) { $root = trim($root); if (str_contains(strtolower($root), 'gamedata')) { $segments = explode('/', trim($root, '/')); return '/' . end($segments); } } } return null; } // ─── PHP Configuration ───────────────────────────────────── public function detectPhpVersion(): string { $result = Process::timeout(5)->run('php -r "echo PHP_MAJOR_VERSION.\'.\'.PHP_MINOR_VERSION;"'); if ($result->successful()) { return trim($result->output()); } return '8.2'; } public function detectPhpIniPath(): ?string { $result = Process::timeout(5)->run('php -i 2>/dev/null | grep "Loaded Configuration File" | cut -d">" -f2 | xargs'); if ($result->successful()) { $path = trim($result->output()); if (file_exists($path)) { return $path; } } return null; } public function detectPhpFpmService(): ?string { $version = $this->detectPhpVersion(); $candidates = [ "php{$version}-fpm", 'php-fpm', 'php8.3-fpm', 'php8.2-fpm', 'php8.1-fpm', 'php8.0-fpm', 'php7.4-fpm', ]; foreach ($candidates as $service) { $check = Process::timeout(5)->run( 'systemctl list-unit-files ' . escapeshellarg($service) . '.service 2>/dev/null | grep -q ' . escapeshellarg($service) . ' && echo found', ); if ($check->successful() && trim($check->output()) === 'found') { return $service; } } return null; } // ─── Java ────────────────────────────────────────────────── public function detectJavaVersion(): ?string { $result = Process::timeout(5)->run('java -version 2>&1 | head -1'); if ($result->successful()) { return trim($result->output()); } return null; } public function detectJavaPath(): ?string { $result = Process::timeout(5)->run('which java 2>/dev/null'); if ($result->successful()) { $path = trim($result->output()); if ($path !== '') { return $path; } } // Check common locations $commonPaths = [ '/usr/bin/java', '/usr/local/bin/java', '/usr/lib/jvm/default/bin/java', ]; foreach ($commonPaths as $path) { if (file_exists($path)) { return $path; } } return null; } // ─── System Tools ────────────────────────────────────────── public function detectGitPath(): ?string { $result = Process::timeout(5)->run('which git 2>/dev/null'); return $result->successful() ? trim($result->output()) : null; } public function detectNodePath(): ?string { $result = Process::timeout(5)->run('which node 2>/dev/null'); return $result->successful() ? trim($result->output()) : null; } public function detectYarnPath(): ?string { $result = Process::timeout(5)->run('which yarn 2>/dev/null'); return $result->successful() ? trim($result->output()) : null; } public function detectNpmPath(): ?string { $result = Process::timeout(5)->run('which npm 2>/dev/null'); return $result->successful() ? trim($result->output()) : null; } public function detectMavenPath(): ?string { $result = Process::timeout(5)->run('which mvn 2>/dev/null || which mvnw 2>/dev/null'); if ($result->successful()) { $path = trim($result->output()); if ($path !== '') { return $path; } } // Check source path for mvnw $sourcePath = $this->detectEmulatorSourcePath(); $mvnw = $sourcePath . '/mvnw'; if (file_exists($mvnw)) { return $mvnw; } return null; } public function detectGradlePath(): ?string { $result = Process::timeout(5)->run('which gradle 2>/dev/null'); if ($result->successful()) { $path = trim($result->output()); if ($path !== '') { return $path; } } $sourcePath = $this->detectEmulatorSourcePath(); $gradlew = $sourcePath . '/gradlew'; if (file_exists($gradlew)) { return $gradlew; } return null; } // ─── Full Detection ──────────────────────────────────────── public function detectAll(): array { return [ // Emulator 'emulator_jar_path' => $this->detectEmulatorJarPath(), 'emulator_source_path' => $this->detectEmulatorSourcePath(), 'emulator_service_name' => $this->detectEmulatorServiceName(), 'emulator_java_process' => $this->detectEmulatorJavaProcess(), // Nitro 'nitro_client_path' => $this->detectNitroClientPath(), 'nitro_renderer_path' => $this->detectNitroRendererPath(), 'nitro_webroot' => $this->detectNitroWebroot(), 'nitro_build_path' => $this->detectNitroBuildPath(), // Gamedata 'gamedata_path' => $this->detectGamedataPath(), 'furniture_icons_path' => $this->detectFurnitureIconsPath(), 'catalog_icons_path' => $this->detectCatalogIconsPath(), // System 'nginx_config_path' => $this->detectNginxConfigPath(), 'php_version' => $this->detectPhpVersion(), 'php_ini_path' => $this->detectPhpIniPath(), 'php_fpm_service' => $this->detectPhpFpmService(), 'java_version' => $this->detectJavaVersion(), 'java_path' => $this->detectJavaPath(), // Tools 'git_path' => $this->detectGitPath(), 'node_path' => $this->detectNodePath(), 'yarn_path' => $this->detectYarnPath(), 'npm_path' => $this->detectNpmPath(), 'maven_path' => $this->detectMavenPath(), 'gradle_path' => $this->detectGradlePath(), ]; } public function clearCache(): void { $this->cache = []; } // ─── Private Helpers ─────────────────────────────────────── private function smartSearch(array $dirNames, array $validators, array $searchDirs): ?string { // Phase 1: exact directory name match with validator check foreach ($searchDirs as $searchDir) { if (! $this->exists($searchDir)) { continue; } foreach ($dirNames as $dirName) { $fullPath = rtrim((string) $searchDir, '/') . '/' . $dirName; if (! $this->exists($fullPath)) { continue; } foreach ($validators as $validator) { if (str_contains((string) $validator, '*')) { $result = Process::timeout(5)->run( 'find ' . escapeshellarg($fullPath) . ' -maxdepth 2 -name ' . escapeshellarg((string) $validator) . ' 2>/dev/null | head -1', ); if ($result->successful() && trim($result->output()) !== '') { return $fullPath; } } elseif ($this->exists($fullPath . '/' . $validator)) { return $fullPath; } } } } // Phase 2: deep search via find foreach ($searchDirs as $searchDir) { if (! $this->exists($searchDir)) { continue; } $namePattern = implode('|', $dirNames); $result = Process::timeout(15)->run( 'find ' . escapeshellarg((string) $searchDir) . ' -maxdepth 4 -type d 2>/dev/null | grep -iE "(' . implode('|', array_map(preg_quote(...), $dirNames)) . ')" | head -5', ); if ($result->successful()) { $lines = explode("\n", trim($result->output())); foreach ($lines as $line) { $line = trim($line); if ($line === '') { continue; } foreach ($validators as $validator) { if (str_contains((string) $validator, '*')) { $check = Process::timeout(5)->run( 'find ' . escapeshellarg($line) . ' -maxdepth 2 -name ' . escapeshellarg((string) $validator) . ' 2>/dev/null | head -1', ); if ($check->successful() && trim($check->output()) !== '') { return $line; } } elseif ($this->exists($line . '/' . $validator)) { return $line; } } } } } return null; } private function exists(string $path): bool { $result = Process::timeout(5)->run( 'test -e ' . escapeshellarg($path) . ' && echo yes || echo no', ); return trim($result->output()) === 'yes'; } private function getSetting(string $key): ?string { try { $settings = resolve(SettingsService::class); $value = $settings->getOrDefault($key, ''); return ($value !== '' && $value !== '0') ? $value : null; } catch (\Exception) { return null; } } private function saveSetting(string $key, string $value): void { try { $settings = resolve(SettingsService::class); $settings->set($key, $value); } catch (\Exception) { // Silently fail - detection still works without saving } } }