'Invalid message.']; $user = $this->security->getUser(); if(!$user instanceof User) return ['error' => 'You are not logged in.']; $player = $this->playerService->GetCurrentlyActiveAsPlayer($user); if(!$player) return ['error' => 'You are not in a game.']; // TODO: Here i need to add a message handler to save the message in a big log. $data = []; if(str_starts_with($message, '/')) { $data = $this->checkGameCommando($message, $player); } else { $data = $this->checkConsoleCommando($message, $player); } return $data; } private function getRechten(Player $player): array { $settingName = SessionSettingType::tryFrom('RightsForPlayer' . $player->getScreen()); if (!$settingName) { return []; } $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $player); if (!$setting || !$setting->getValue()) { return []; } return json_decode($setting->getValue(), true) ?? []; } private function checkGameCommando(string $message, Player $player) : array { $messagePart = explode(' ', $message); $rechten = $this->getRechten($player); switch($messagePart[0]) { case '/chat': if(!in_array('chat', $rechten)) return ['result' => ['Unknown command']]; if($this->handleChatMessage($message, $player)) return ['result' => ['succesfully send']]; else return ['result' => ['Error sending']]; case '/help': return ['result' => $this->getHelpCommand($rechten)]; case '/decode': if(!in_array('decode', $rechten)) return ['result' => ['Unknown command']]; return ['result' => [$this->handleDecodeMessage($messagePart[1], $player)]]; case '/verify': if(!in_array('verify', $rechten)) return ['result' => ['Unknown command']]; $result = $this->handleVerifyMessage($message, $player); return ['result' => [$result]]; default: return ['result' => ['Unknown command']]; } } private function checkConsoleCommando(string $message, Player $player, bool $sudo = false) : array { $messagePart = explode(' ', $message); $rechten = $this->getRechten($player); switch($messagePart[0]) { case 'help': return ['result' => $this->getHelpCommand($rechten)]; case 'ls': if(!in_array('ls', $rechten)) return ['result' => ['Unknown command']]; $files = $this->getAllCurrentFilesInDirectory($player); return ['result' => $files]; case 'cd': if(!in_array('cd', $rechten)) return ['result' => ['Unknown command']]; $pwd = $this->playerService->getCurrentPwdOfPlayer($player); if(!$pwd) return ['result' => ['Unknown command']]; $newLocation = $this->goToNewDir($pwd, $messagePart[1], $player); if($newLocation === false) return ['result' => ['Unknown path']]; $this->playerService->saveCurrentPwdOfPlayer($player, $newLocation); return ['result' => ['Path: ' . $newLocation]]; case 'pwd': if(!in_array('pwd', $rechten)) return ['result' => ['Unknown command']]; $pwd = $this->playerService->getCurrentPwdOfPlayer($player); return ['result' => ['Path: ' . $pwd]]; case 'rm': if(!in_array('rm', $rechten)) return ['result' => ['Unknown command']]; $pwd = $this->playerService->getCurrentPwdOfPlayer($player); if(!$pwd) return ['result' => ['Unknown command']]; if (!isset($messagePart[1])) { return ['result' => ['Usage: rm {filename}']]; } $filename = $messagePart[1]; $fullPath = ($pwd === '/' ? '' : $pwd) . '/' . $filename; if(!$this->isAllowedToRemove($fullPath, $player, $sudo)) return ['result' => ['You are not allowed to remove this file.']]; $this->playerService->addDeletedFileToSession($player, $fullPath); return ['result' => ['File removed: ' . $filename]]; case 'sudo': if(!in_array('sudo', $rechten)) return ['result' => ['Unknown command']]; $sudo = array_shift($messagePart); $message = implode(' ', $messagePart); return $this->checkConsoleCommando($message, $player, true); default: return ['result' => ['Unknown command']]; } } private function getHelpCommand(mixed $rechten) : array { $messages = []; foreach($rechten as $recht) { switch($recht) { case 'chat': $messages[] = '/chat'; $messages[] = ' Use /chat {message} to send the message to the other agents.'; $messages[] = ' If you want to send a message specifically to one other agent, use the id of the agent after /chat, like /chat 6 {message}'; $messages[] = ' This will send the message only to agent with id 6.'; $messages[] = ' USAGE: /chat {message}'; $messages[] = ' USAGE: /chat 6 {message}'; $messages[] = ''; break; case 'help': $messages[] = '/help'; $messages[] = ' This shows this help.'; $messages[] = ' USAGE: /help'; $messages[] = ''; break; case 'decode': $messages[] = '/decode'; $messages[] = ' This message will decode the message followed by it.'; $messages[] = ' Every agent has a different way to decode messages. This is a security measure. The AI Virus has no access to all decoders.'; $messages[] = ' USAGE: /decode {message}'; $messages[] = ''; break; case 'pwd': $messages[] = 'pwd'; $messages[] = ' This message will let you know what your current location is.'; $messages[] = ' It will show you the folder you are in so you can continue navigating the server.'; $messages[] = ' USAGE: pwd'; $messages[] = ''; break; case 'cat': $messages[] = 'cat'; $messages[] = ' To read a file, use cat {filename}.'; $messages[] = ' This will print the full content of the file on the screen.'; $messages[] = ' USAGE: cat {filename}'; $messages[] = ''; break; case 'ls': $messages[] = 'ls'; $messages[] = ' To show all the files in the current directory, use ls.'; $messages[] = ' This will print the full list of directories and files of the current location on your screen.'; $messages[] = ' USAGE: ls'; $messages[] = ''; break; case 'rm': $messages[] = 'rm'; $messages[] = ' Use rm to delete a file.'; $messages[] = ' Be careful with this command. It can not be undone and we do not want to lose any valuable data.'; $messages[] = ' USAGE: rm {filename}'; $messages[] = ''; break; case 'cd': $messages[] = 'cd'; $messages[] = ' Use cd to move to a different directory.'; $messages[] = ' You can go into a folder by using cd {foldername}, or a folder up by using "cd ..".'; $messages[] = ' Using cd / moves you to the root directory.'; $messages[] = ' USAGE: cd {directory}'; $messages[] = ''; break; case 'sudo': $messages[] = 'sudo'; $messages[] = ' If you do not have enough rights to execute a command, you can use sudo to execute it as root.'; $messages[] = ' This is only possible for verified users. To verify yourself, use the /verify command.'; $messages[] = ' USAGE: sudo {command}'; $messages[] = ''; break; case 'verify': $messages[] = '/verify'; $messages[] = ' You can verify yourself by using this command.'; $messages[] = ' Use this command and follow instructions to verify yourself.'; $messages[] = ' USAGE: /verify'; $messages[] = ''; break; } } return $messages; } private function handleChatMessage(string $message, Player $player) : bool { $messageParts = explode(' ', $message); $toSingle = false; if (isset($messageParts[1]) && is_numeric($messageParts[1]) && $messageParts[1] >= 1 && $messageParts[1] <= $player->getSession()->getGame()->getNumberOfPlayers()) { $toSingle = true; } $chatMessage = array_shift($messageParts); $sendTo = 0; if ($toSingle) { $sendTo = array_shift($messageParts); $chatMessage = array_shift($messageParts); } $message = $player->getUser()->getUsername() . ': ' . $chatMessage . ' '; foreach($messageParts as $messagePart) { $message .= $messagePart . ' '; } $message = trim($message); $activeGame = $player->getSession()?->getId(); if(is_null($activeGame)) return false; $topic = $_ENV['MERCURE_TOPIC_BASE'] . '/game/hub-' . $activeGame; $this->hub->publish(new Update($topic, json_encode([$sendTo, $message]))); $this->updateChatTracking($player, (int)$sendTo); $this->checkAndRegenerateVerifyCodes($player, $chatMessage . ' ' . implode(' ', $messageParts)); return true; } private function checkAndRegenerateVerifyCodes(Player $player, string $messageContent): void { $screen = $player->getScreen(); $session = $player->getSession(); $verifyCodesSettingName = SessionSettingType::tryFrom('VerifyCodesForPlayer' . $screen); if (!$verifyCodesSettingName) { return; } $setting = $this->sessionSettingRepository->getSetting($session, $verifyCodesSettingName, $player); if (!$setting) { return; } $codes = json_decode($setting->getValue() ?? '[]', true) ?? []; $regenerated = false; foreach ($codes as $targetPlayerScreen => $code) { if (str_contains($messageContent, (string)$code)) { $codes[$targetPlayerScreen] = bin2hex(random_bytes(3)); $regenerated = true; } } if ($regenerated) { $setting->setValue(json_encode($codes)); $this->entityManager->persist($setting); $this->entityManager->flush(); // Notify the player that their codes have changed $topic = $_ENV['MERCURE_TOPIC_BASE'] . '/game/hub-' . $session->getId(); $notification = "Security Alert: One of your verify codes was shared and has been regenerated."; // We send it only to this player (screen) $this->hub->publish(new Update($topic, json_encode([$screen, $notification]))); } } private function updateChatTracking(Player $player, int $sendTo): void { $rights = $this->getRechten($player); if(in_array('verify', $rights)) return; $trackingSettingName = SessionSettingType::tryFrom('ChatTrackingForPlayer' . $player->getScreen()); if (!$trackingSettingName) { return; } $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $trackingSettingName, $player); if (!$setting) { $setting = new SessionSetting(); $setting->setSession($player->getSession()); $setting->setPlayer($player); $setting->setName($trackingSettingName); $setting->setValue(json_encode([])); } $tracking = json_decode($setting->getValue() ?? '[]', true) ?? []; if (!in_array($sendTo, $tracking)) { $tracking[] = $sendTo; $setting->setValue(json_encode($tracking)); $this->entityManager->persist($setting); $this->entityManager->flush(); $this->checkAndGrantVerifyRight($player, $tracking); } } private function checkAndGrantVerifyRight(Player $player, array $tracking): void { $screen = $player->getScreen(); $requiredTargets = [0]; // Everyone $numPlayers = $player->getSession()->getGame()->getNumberOfPlayers(); for ($i = 1; $i <= $numPlayers; $i++) { if ($i !== $screen) { $requiredTargets[] = $i; } } // Check if all required targets are in tracking foreach ($requiredTargets as $target) { if (!in_array($target, $tracking)) { return; } } // Grant verify right $rightsSettingName = SessionSettingType::tryFrom('RightsForPlayer' . $screen); if (!$rightsSettingName) { return; } $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $rightsSettingName, $player); if (!$setting) { return; // Should have been initialized } $rights = json_decode($setting->getValue() ?? '[]', true) ?? []; $newRights = ['verify', 'cat']; $updated = false; foreach ($newRights as $newRight) { if (!in_array($newRight, $rights)) { $rights[] = $newRight; $updated = true; } } if ($updated) { $setting->setValue(json_encode($rights)); $this->entityManager->persist($setting); $this->entityManager->flush(); } } private function handleDecodeMessage(string $message, Player $player) { $userNumber = $player->getScreen(); preg_match('/\d+/', $message, $matches); $num = $matches[0] ?? null; $randomString = $this->generateRandomString(250, 500); if(is_null($num) || $num != $userNumber) return $randomString; foreach (DecodeMessage::cases() as $decodeMessage) { if ($decodeMessage->name === $message) { return $decodeMessage->value; } } return $randomString; } private function generateRandomString(int $min, int $max): string { $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ '; $charactersLength = strlen($characters); $randomString = ''; $length = random_int($min, $max); for ($i = 0; $i < $length; $i++) { $randomString .= $characters[random_int(0, $charactersLength - 1)]; } return $randomString; } private function handleVerifyMessage(string $message, Player $player) : string { $messageParts = explode(' ', $message); if (count($messageParts) < 2) { return 'Usage: /verify {code}'; } $code = $messageParts[1]; $screen = $player->getScreen(); $session = $player->getSession(); $progressSettingName = SessionSettingType::tryFrom('VerificationProgressForPlayer' . $screen); if (!$progressSettingName) { return 'Error: Invalid player screen.'; } $progressSetting = $this->sessionSettingRepository->getSetting($session, $progressSettingName, $player); if (!$progressSetting) { return 'Error: Verification progress setting not found.'; } $progress = json_decode($progressSetting->getValue() ?? '[]', true) ?? []; $verifiedBy = null; foreach ($session->getPlayers() as $otherPlayer) { if ($otherPlayer->getId() === $player->getId()) { continue; } $otherScreen = $otherPlayer->getScreen(); $codesSettingName = SessionSettingType::tryFrom('VerifyCodesForPlayer' . $otherScreen); if (!$codesSettingName) { continue; } $codesSetting = $this->sessionSettingRepository->getSetting($session, $codesSettingName, $otherPlayer); if (!$codesSetting) { continue; } $codes = json_decode($codesSetting->getValue() ?? '[]', true) ?? []; if (isset($codes[$screen]) && $codes[$screen] === $code) { $verifiedBy = $otherScreen; break; } } if ($verifiedBy !== null) { if (!in_array($verifiedBy, $progress)) { $progress[] = $verifiedBy; $progressSetting->setValue(json_encode($progress)); $this->entityManager->persist($progressSetting); $this->entityManager->flush(); $response = 'You have been successfully verified by Agent ' . $verifiedBy . '.'; if (count($progress) >= 2) { $this->grantVerificationRights($player); $response .= ' You have received additional rights!'; } return $response; } else { return 'You were already verified by Agent ' . $verifiedBy . '.'; } } return 'Invalid verification code.'; } private function grantVerificationRights(Player $player): void { $screen = $player->getScreen(); $rightsSettingName = SessionSettingType::tryFrom('RightsForPlayer' . $screen); if (!$rightsSettingName) { return; } $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $rightsSettingName, $player); if (!$setting) { return; } $rights = json_decode($setting->getValue() ?? '[]', true) ?? []; $newRights = ['cd', 'decode']; $updated = false; foreach ($newRights as $newRight) { if (!in_array($newRight, $rights)) { $rights[] = $newRight; $updated = true; } } if ($updated) { $setting->setValue(json_encode($rights)); $this->entityManager->persist($setting); $this->entityManager->flush(); $this->checkIfAllPlayersVerified($player); } } private function checkIfAllPlayersVerified(Player $player): void { $session = $player->getSession(); $everyoneVerifiedSetting = $this->sessionSettingRepository->getSetting($session, SessionSettingType::EVERYONE_VERIFIED, $player); if ($everyoneVerifiedSetting && $everyoneVerifiedSetting->getValue() === 'true') { return; } $allVerified = true; foreach ($session->getPlayers() as $otherPlayer) { $otherScreen = $otherPlayer->getScreen(); $progressSettingName = SessionSettingType::tryFrom('VerificationProgressForPlayer' . $otherScreen); if (!$progressSettingName) { continue; } $progressSetting = $this->sessionSettingRepository->getSetting($session, $progressSettingName, $otherPlayer); $progress = json_decode($progressSetting?->getValue() ?? '[]', true) ?? []; if (count($progress) < $session->getGame()->getNumberOfPlayers() - 1) { $allVerified = false; break; } } if ($allVerified) { if (!$everyoneVerifiedSetting) { $everyoneVerifiedSetting = new SessionSetting(); $everyoneVerifiedSetting->setSession($session); $everyoneVerifiedSetting->setPlayer($player); $everyoneVerifiedSetting->setName(SessionSettingType::EVERYONE_VERIFIED); } $everyoneVerifiedSetting->setValue('true'); $this->entityManager->persist($everyoneVerifiedSetting); $this->entityManager->flush(); $topic = $_ENV['MERCURE_TOPIC_BASE'] . '/game/hub-' . $session->getId(); $message = "Mainframe Help Modus: Agents Doyle, Vega and Lennox rapports have been updated with coded messages."; $this->hub->publish(new Update($topic, json_encode([0, $message]))); } } private function goToNewDir(string $pwd, string $newPwd, Player $player) : string|bool { $allPossiblePaths = $this->getAllPossiblePaths($player); $dirParts = explode('/', $newPwd); $int = count($dirParts); if($dirParts[0] == '') { $newDir = ''; $startPart = 1; } else { $newDir = $pwd; $startPart = 0; } for($i = $startPart; $i < $int; $i++) { if($dirParts[$i] == '..') $newDir = $this->getPrevPath($newDir); else $newDir .= '/' . $dirParts[$i]; if(!in_array($newDir, $allPossiblePaths)) return false; } return $newDir; } private function getPrevPath(string $pwd) : string { $pwdParts = explode('/', $pwd); array_pop($pwdParts); $pwd = implode('/', $pwdParts); return $pwd; } private function getAllPossiblePaths(Player $player) : array { $paths = []; $paths[] = '/'; $paths[] = '/var'; $paths[] = '/var/arrest'; $paths[] = '/var/www'; $paths[] = '/var/marriage'; $paths[] = '/var/rapports'; $paths[] = '/var/linking'; $paths[] = '/etc'; $paths[] = '/etc/short'; $paths[] = '/etc/long'; $paths[] = '/etc/arrest'; $paths[] = '/etc/power'; $paths[] = '/etc/break'; $paths[] = '/etc/handle'; $paths[] = '/etc/freak'; $paths[] = '/etc/host'; $paths[] = '/home'; $playerNames = ['root', 'Luke', 'Charles', 'William', 'Peter']; $players = $player->getSession()->getPlayers(); foreach($players as $player) { $playerNames[] = $player->getUser()->getUsername(); } $playerNames = array_unique($playerNames); foreach($playerNames as $name) { $paths[] = '/home/' . $name; } return $paths; } private function isAllowedToRemove(string $file, Player $player, bool $sudo) : bool { if(!$this->fileExists($file, $player)) return false; if(str_starts_with($file, '/var/rapports/')) return false; $rights = $this->getRechten($player); if(in_array('sudo', $rights) || $sudo) return true; $sudoFiles = [ '/var/arrest/handle.sh', '/var/arrest/cell.sh', '/var/marriage/divorce.sh', ]; return !in_array($file, $sudoFiles); } private function fileExists(string $file, Player $player) : bool { $files = $this->getAllPossibleFiles($player); if(in_array($file, $files)) return true; return false; } private function getAllPossibleFiles(Player $player = null) : array { $files = []; $files[] = '/var/arrest/handle.sh'; $files[] = '/var/arrest/bars.sh'; $files[] = '/var/arrest/cell.sh'; $files[] = '/var/marriage/share.sh'; $files[] = '/var/marriage/divorce.sh'; $files[] = '/var/rapports/095_07-14.txt'; $files[] = '/var/rapports/007_19-52.txt'; $files[] = '/var/rapports/083_25-39.txt'; $files[] = '/var/rapports/019_31-11.txt'; $files[] = '/var/rapports/075_46-77.txt'; $files[] = '/var/rapports/031_53-28.txt'; $files[] = '/var/rapports/072_61-05.txt'; $files[] = '/var/rapports/064_72-90.txt'; $files[] = '/var/rapports/091_81-33.txt'; $files[] = '/var/rapports/079_89-47.txt'; $files[] = '/var/rapports/098_92-14.txt'; $files[] = '/var/rapports/012_94-31.txt'; $files[] = '/var/rapports/016_98-07.txt'; $files[] = '/var/rapports/087_102-45.txt'; $files[] = '/var/rapports/094_110-19.txt'; $files[] = '/var/rapports/063_117-56.txt'; $files[] = '/var/rapports/017_123-88.txt'; $files[] = '/var/rapports/093_138-24.txt'; $files[] = '/var/rapports/001_145-93.txt'; $files[] = '/var/rapports/index.txt'; $players = $player->getSession()->getPlayers(); foreach($players as $player) { $files[] = '/var/home/' . $player->getUser()->getUsername() . '/verifyCodes.txt'; } return $files; } private function getAllCurrentFilesInDirectory(Player $player) : array { $pwd = $this->playerService->getCurrentPwdOfPlayer($player); if (!$pwd) { return []; } $allPaths = $this->getAllPossiblePaths($player); $allFiles = $this->getAllPossibleFiles(); $deletedFiles = $this->playerService->getDeletedFilesOfSession($player); $entries = []; // Find directories in current pwd foreach ($allPaths as $path) { if ($path === $pwd) { continue; } // Check if $path is a direct child of $pwd $parent = $this->getPrevPath($path); if ($parent === $pwd) { $parts = explode('/', $path); $entries[] = [end($parts) . '/', 'dir']; } } // Find files in current pwd foreach ($allFiles as $file) { if (in_array($file, $deletedFiles)) { continue; } $parent = $this->getPrevPath($file); if ($parent === $pwd) { $parts = explode('/', $file); $entries[] = [end($parts), 'file']; } } sort($entries); return $entries; } }