├── .gitignore ├── README.md ├── app.js ├── bin └── www ├── package.json ├── public ├── index.html ├── js │ ├── commands.js │ └── jquery.term.js └── stylesheets │ ├── jquery.terminal.css │ └── style.css ├── routes ├── index.js └── users.js └── views ├── error.jade ├── index.jade ├── layout.jade └── main.jade /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HackShell 2 | ============ 3 | 4 | **HackShell** is a terminal simulator hacking game built in a 24-hour period at [CodeDay LA Spring 2014](http://codeday.org). 5 | 6 | 7 | The goal of the project was to create a fun game that introduces new users to the command line, through interactive levels where you play as a corporate hacker completing daring missions of corporate espionage. Learn common terminal commands and level up in this thrilling Cyberpunk adventure! 8 | 9 | --- 10 | 11 | Written in node.js and makes some use of [jquery.terminal](https://github.com/jcubic/jquery.terminal) for the terminal interface. 12 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('static-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'jade'); 16 | 17 | app.use(favicon()); 18 | app.use(logger('dev')); 19 | app.use(bodyParser.json()); 20 | app.use(bodyParser.urlencoded()); 21 | app.use(cookieParser()); 22 | app.use(express.static(path.join(__dirname, 'public'))); 23 | 24 | app.use('/', function(req, res) { 25 | res.sendfile(path.join(__dirname, 'public/index.html')); 26 | }); 27 | app.use('/users', users); 28 | 29 | /// catch 404 and forward to error handler 30 | app.use(function(req, res, next) { 31 | var err = new Error('Not Found'); 32 | err.status = 404; 33 | next(err); 34 | }); 35 | 36 | /// error handlers 37 | 38 | // development error handler 39 | // will print stacktrace 40 | if (app.get('env') === 'development') { 41 | app.use(function(err, req, res, next) { 42 | res.status(err.status || 500); 43 | res.render('error', { 44 | message: err.message, 45 | error: err 46 | }); 47 | }); 48 | } 49 | 50 | // production error handler 51 | // no stacktraces leaked to user 52 | app.use(function(err, req, res, next) { 53 | res.status(err.status || 500); 54 | res.render('error', { 55 | message: err.message, 56 | error: {} 57 | }); 58 | }); 59 | 60 | 61 | module.exports = app; 62 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('HackTerminal'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HackTerminal", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "express": "~4.2.0", 10 | "static-favicon": "~1.0.0", 11 | "morgan": "~1.0.0", 12 | "cookie-parser": "~1.0.1", 13 | "body-parser": "~1.0.0", 14 | "debug": "~0.7.4", 15 | "jade": "~1.3.0" 16 | } 17 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hackshell 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 14 | 16 | 17 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/js/commands.js: -------------------------------------------------------------------------------- 1 | var curDir = "~" 2 | 3 | var baseUrl = 'http://' + window.location.host; 4 | var dirs = { 5 | "~" : "Missions/", 6 | "~/Missions" : "" 7 | } 8 | var userfiles = { 9 | "~" : "readme.txt", 10 | "~/readme.txt" : "Hello there Mr.Kitnick, I'm Edmen Snowman and I'll be \nyour manager, prepare for utter death and destruction! (I'm kidding you know) \n\ 11 | \n\ 12 | This may be only your first day on the job, but we're in need of people like you \nso you better get started right away, in fact \nI may have a mission ready for you, check for it in your 'Missions' folder.", 13 | "~/Missions" : "mission1.txt", 14 | "~/Missions/mission1.txt" : "\n\nMission #1\n\ 15 | ----------\n\n\ 16 | Your mission, should you choose to accept it, is to find information \nfrom the computer of one of our own employees, Michael Groves.\n\ 17 | Mr. Groves is suspected of working for our rival, Cortex Industries. \nCortex is in many ways a similar company to NitroSoft, but they are \nbuilding a so-called 'life improvement framework' which we beleive they may be planning \non using for brainwashing of all who use their services.\n\n\ 18 | Procedure:\n\ 19 | ----------\n\n\ 20 | You will need to get into Groves's system, luckily we happen to have a\n\ 21 | security camera right above all employee's desk, as is standard practice\n\ 22 | and we captured " + baseUrl + "/clickable/test.html" + " picture, see if you can find anything from it.\n\ 23 | When you have aquired his password, use the 'ssh' command like so to get into his system:\n\ 24 | ssh mgroves@cortex.pizza " 25 | 26 | } 27 | 28 | var commands = function (input, cb) { 29 | var inParts = input.split(' '); 30 | var cmd = inParts[0]; 31 | if(cmd === 'ls' || cmd === 'dir') { 32 | var lsr = ls(inParts[1]); 33 | return cb(lsr[0], lsr[1]); 34 | } else if(cmd === 'cd' || cmd === 'cd..') { 35 | if (cmd === 'cd..') { 36 | var cdr = cd("..") 37 | } else { 38 | var cdr = cd(inParts[1]); 39 | } 40 | return cb(cdr[0], cdr[1]); 41 | } else if(cmd === 'ssh') { 42 | var sshr = ssh(inParts[1], inParts[2], cb); 43 | } else if(cmd === 'help' || cmd === '?') { 44 | return cb(null, help()); 45 | } else if(cmd === 'mail') { 46 | // Mail 47 | } else if(cmd === 'cat') { 48 | // Echo file 49 | var catr = cat(inParts[1]); 50 | return cb(catr[0], catr[1]); 51 | } else { 52 | return cb('No such function'); 53 | } 54 | } 55 | 56 | // Callback structure: 57 | // cb(err, data); 58 | 59 | var ssh = function(whereto, pass, cb) { 60 | if (whereto === "mgroves@cortex.pizza" && pass === 'password1') { 61 | return cb(null, "Success! You have completed mission #1!"); 62 | } 63 | return cb('Wrong user or pass!'); 64 | } 65 | 66 | var help = function(cb) { 67 | var info = " ls - List files in current directory.\n \ 68 | cd - Change to directory .\n \ 69 | help - This.\n \ 70 | cat - Output the contents of a file.\n \ 71 | To get started, use the ls command to find any files, and cat to read them"; 72 | return [null, info]; 73 | } 74 | 75 | var ls = function(dir) { 76 | if (!dir){ 77 | dir = curDir; 78 | } 79 | var dirlist = dirs[dir]; 80 | var filelist = userfiles[dir]; 81 | if (! dirlist){ 82 | var list = [filelist]; 83 | } else if (! filelist) { 84 | var list = [dirlist]; 85 | } else { 86 | var list = [dirlist + "\n" + filelist]; 87 | } 88 | return [null, list]; 89 | } 90 | 91 | var cd = function(dir) { 92 | if (! dir) { 93 | curDir = "~"; 94 | return [null, null]; 95 | } 96 | if (dir.slice(-1) === "/") { 97 | dir = dir.substring(0, dir.length - 1); 98 | } 99 | if (dir === "..") { 100 | if (curDir === "~") { 101 | return ["Restricted, you may not go above your home directory"]; 102 | } 103 | finDir = curDir.split("/"); 104 | finDir.pop(); 105 | curDir = finDir.join("/"); 106 | } else { 107 | console.log(dirs[dir]); 108 | curDir = curDir + "/" + dir; 109 | } 110 | return [null, curDir]; 111 | } 112 | 113 | var cat = function (file) { 114 | file = curDir + "/" + file; 115 | return [null, userfiles[file]] 116 | } 117 | -------------------------------------------------------------------------------- /public/stylesheets/jquery.terminal.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This css file is part of jquery terminal 3 | * 4 | * Licensed under GNU LGPL Version 3 license 5 | * Copyright (c) 2011-2013 Jakub Jankiewicz 6 | * 7 | */ 8 | .terminal .terminal-output .format, .cmd .format, 9 | .cmd .prompt, .cmd .prompt div, .terminal .terminal-output div div{ 10 | display: inline-block; 11 | /*height: 100% !important;*/ 12 | } 13 | .cmd .clipboard { 14 | position: absolute; 15 | bottom: 0; 16 | left: 0; 17 | opacity: 0.01; 18 | filter: alpha(opacity = 0.01); 19 | filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0.01); 20 | width: 2px; 21 | /*height: 100% !important;*/ 22 | } 23 | *{ 24 | margin: 0; 25 | padding: 0; 26 | } 27 | /*.scanlines { 28 | background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAADElEQVQImWNggAJjAAA9ADTPmgqGAAAAAElFTkSuQmCC) repeat; 29 | z-index: 100000; 30 | }*/ 31 | .cmd > .clipboard { 32 | position: fixed; 33 | } 34 | .terminal { 35 | padding: 10px; 36 | position: relative; 37 | overflow: hidden; 38 | /*height: 100% !important;*/ 39 | } 40 | .cmd { 41 | padding: 0; 42 | margin: 0; 43 | height: 1.3em; 44 | /*margin-top: 3px; */ 45 | } 46 | .cmd .cursor.blink { 47 | -webkit-animation: blink 1s infinite steps(1, start); 48 | -moz-animation: blink 1s infinite steps(1, start); 49 | -ms-animation: blink 1s infinite steps(1, start); 50 | animation: blink 1s infinite steps(1, start); 51 | } 52 | @keyframes blink { 53 | 0%, 100% { 54 | background-color: #000; 55 | color: #aaa; 56 | } 57 | 50% { 58 | background-color: #bbb; /* not #aaa because it's seem there is Google Chrome bug */ 59 | color: #000; 60 | } 61 | } 62 | @-webkit-keyframes blink { 63 | 0%, 100% { 64 | background-color: #000; 65 | color: #aaa; 66 | } 67 | 50% { 68 | background-color: #bbb; 69 | color: #000; 70 | } 71 | } 72 | @-ms-keyframes blink { 73 | 0%, 100% { 74 | background-color: #000; 75 | color: #aaa; 76 | } 77 | 50% { 78 | background-color: #bbb; 79 | color: #000; 80 | } 81 | } 82 | @-moz-keyframes blink { 83 | 0%, 100% { 84 | background-color: #000; 85 | color: #aaa; 86 | } 87 | 50% { 88 | background-color: #bbb; 89 | color: #000; 90 | } 91 | } 92 | .terminal .terminal-output div div, .cmd .prompt { 93 | display: block; 94 | line-height: 25px; 95 | height: auto; 96 | } 97 | .cmd .prompt { 98 | float: left; 99 | } 100 | $scanLineWidth: 1px; 101 | .scanLines { 102 | /*** WEBKIT ***/ 103 | background: -webkit-repeating-linear-gradient( 104 | top, 105 | transparent 0px, 106 | transparent $scanLineWidth, 107 | rgba(0,0,0,0.25) $scanLineWidth, 108 | rgba(0,0,0,0.25) $scanLineWidth*2 109 | ); 110 | -webkit-background-size: 100% $scanLineWidth*2; 111 | /** MOZILLA **/ 112 | background: -moz-repeating-linear-gradient( 113 | top, 114 | transparent 0px, 115 | transparent $scanLineWidth, 116 | rgba(0,0,0,0.25) $scanLineWidth, 117 | rgba(0,0,0,0.25) $scanLineWidth*2 118 | ); 119 | -moz-background-size: 100% $scanLineWidth*2; 120 | } 121 | .terminal, .cmd { 122 | font-family: Courier New, monospace; 123 | color: #1f1; 124 | text-shadow: 2px 1px 1px #F05, 125 | -2px -1px 1px #05F; 126 | background-color: #111; 127 | font-size: 20px; 128 | line-height: 25px; 129 | font-weight: bold; 130 | height 100% !important; 131 | } 132 | .terminal-output > div { 133 | /*padding-top: 3px;*/ 134 | min-height: 14px; 135 | } 136 | .terminal .terminal-output div span { 137 | display: inline-block; 138 | } 139 | .cmd span { 140 | float: left; 141 | /*display: inline-block; */ 142 | } 143 | .terminal .inverted, .cmd .inverted, .cmd .cursor.blink { 144 | background-color: #aaa; 145 | color: #000; 146 | } 147 | .terminal .terminal-output div div::-moz-selection, 148 | .terminal .terminal-output div span::-moz-selection, 149 | .terminal .terminal-output div div a::-moz-selection { 150 | background-color: #aaa; 151 | color: #000; 152 | } 153 | .terminal .terminal-output div div::selection, 154 | .terminal .terminal-output div div a::selection, 155 | .terminal .terminal-output div span::selection, 156 | .cmd > span::selection, 157 | .cmd .prompt span::selection { 158 | background-color: #aaa; 159 | color: #000; 160 | } 161 | .terminal .terminal-output div.error, .terminal .terminal-output div.error div { 162 | color: red; 163 | } 164 | .tilda { 165 | position: fixed; 166 | top: 0; 167 | left: 0; 168 | width: 100%; 169 | z-index: 1100; 170 | } 171 | .clear { 172 | clear: both; 173 | } 174 | .terminal a { 175 | color: #0F60FF; 176 | } 177 | .terminal a:hover { 178 | color: red; 179 | } 180 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 24px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | html { 7 | background-color: #111 !important; 8 | } 9 | 10 | a { 11 | color: #00B7FF; 12 | } 13 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /views/main.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js') 5 | script(src='http://terminal.jcubic.pl/js/jquery.terminal-0.8.7.min.js') 6 | script(src='https://raw.githubusercontent.com/HackShellApp/HackShellWeb/master/commands.js') 7 | script(src='https://gist.githubusercontent.com/caffeinewriter/efa9f1ca20f0eb841d35/raw/cd95a39a332f378e89c5c29e84314508d692a944/gistfile1.js') 8 | body 9 | #terminal 10 | --------------------------------------------------------------------------------