mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-11 18:23:54 +01:00
2946 lines
92 KiB
JavaScript
2946 lines
92 KiB
JavaScript
import {
|
|
evaluateDirectoryPath,
|
|
evaluateFilePath,
|
|
getFirstParentDirectory,
|
|
isInRootDirectory,
|
|
isValidDirectoryPath,
|
|
removeLeadingSlash,
|
|
removeTrailingSlash,
|
|
} from "./Terminal/DirectoryHelpers";
|
|
import { determineAllPossibilitiesForTabCompletion } from "./Terminal/determineAllPossibilitiesForTabCompletion";
|
|
import { TerminalHelpText, HelpTexts } from "./Terminal/HelpText";
|
|
import { tabCompletion } from "./Terminal/tabCompletion";
|
|
import { createFconf } from "./Fconf/Fconf";
|
|
|
|
import {
|
|
parseAliasDeclaration,
|
|
printAliases,
|
|
removeAlias,
|
|
substituteAliases,
|
|
} from "./Alias";
|
|
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
|
|
import { CodingContractResult } from "./CodingContracts";
|
|
import { CONSTANTS } from "./Constants";
|
|
import { Programs } from "./Programs/Programs";
|
|
import {
|
|
executeDarkwebTerminalCommand,
|
|
checkIfConnectedToDarkweb,
|
|
} from "./DarkWeb/DarkWeb";
|
|
import { Engine } from "./engine";
|
|
import { FconfSettings } from "./Fconf/FconfSettings";
|
|
import {
|
|
calculateHackingChance,
|
|
calculateHackingExpGain,
|
|
calculatePercentMoneyHacked,
|
|
calculateHackingTime,
|
|
calculateGrowTime,
|
|
calculateWeakenTime,
|
|
} from "./Hacking";
|
|
import { HacknetServer } from "./Hacknet/HacknetServer";
|
|
import {
|
|
iTutorialNextStep,
|
|
iTutorialSteps,
|
|
ITutorial,
|
|
} from "./InteractiveTutorial";
|
|
import { showLiterature } from "./Literature/LiteratureHelpers";
|
|
import { Message } from "./Message/Message";
|
|
import { showMessage } from "./Message/MessageHelpers";
|
|
import { startWorkerScript } from "./NetscriptWorker";
|
|
import { killWorkerScript } from "./Netscript/killWorkerScript";
|
|
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
|
|
import { Player } from "./Player";
|
|
import { hackWorldDaemon } from "./RedPill";
|
|
import { RunningScript } from "./Script/RunningScript";
|
|
import { compareArrays } from "../utils/helpers/compareArrays";
|
|
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
|
|
import {
|
|
findRunningScript,
|
|
findRunningScriptByPid,
|
|
} from "./Script/ScriptHelpers";
|
|
import { isScriptFilename } from "./Script/ScriptHelpersTS";
|
|
import { AllServers } from "./Server/AllServers";
|
|
import {
|
|
GetServerByHostname,
|
|
getServer,
|
|
getServerOnNetwork,
|
|
} from "./Server/ServerHelpers";
|
|
import {
|
|
SpecialServerIps,
|
|
SpecialServerNames,
|
|
} from "./Server/SpecialServerIps";
|
|
import { setTimeoutRef } from "./utils/SetTimeoutRef";
|
|
import { Page, routing } from "./ui/navigationTracking";
|
|
import { numeralWrapper } from "./ui/numeralFormat";
|
|
import { KEY } from "../utils/helpers/keyCodes";
|
|
import { arrayToString } from "../utils/helpers/arrayToString";
|
|
import { getTimestamp } from "../utils/helpers/getTimestamp";
|
|
import { logBoxCreate } from "../utils/LogBox";
|
|
import {
|
|
yesNoBoxCreate,
|
|
yesNoBoxGetYesButton,
|
|
yesNoBoxGetNoButton,
|
|
yesNoBoxClose,
|
|
} from "../utils/YesNoBox";
|
|
import {
|
|
post,
|
|
postElement,
|
|
postContent,
|
|
postError,
|
|
hackProgressBarPost,
|
|
hackProgressPost,
|
|
} from "./ui/postToTerminal";
|
|
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
|
|
import { Money } from "./ui/React/Money";
|
|
|
|
import autosize from "autosize";
|
|
import * as JSZip from "jszip";
|
|
import * as FileSaver from "file-saver";
|
|
import * as libarg from "arg";
|
|
import React from "react";
|
|
import ReactDOM from "react-dom";
|
|
|
|
function postNetburnerText() {
|
|
post("Bitburner v" + CONSTANTS.Version);
|
|
}
|
|
|
|
// Helper function that checks if an argument (which is a string) is a valid number
|
|
function isNumber(str) {
|
|
if (typeof str != "string") {
|
|
return false;
|
|
} // Only process strings
|
|
return !isNaN(str) && !isNaN(parseFloat(str));
|
|
}
|
|
|
|
function getTerminalInput() {
|
|
return document.getElementById("terminal-input-text-box").value;
|
|
}
|
|
|
|
// Defines key commands in terminal
|
|
$(document).keydown(function (event) {
|
|
// Terminal
|
|
if (routing.isOn(Page.Terminal)) {
|
|
var terminalInput = document.getElementById("terminal-input-text-box");
|
|
if (
|
|
terminalInput != null &&
|
|
!event.ctrlKey &&
|
|
!event.shiftKey &&
|
|
!Terminal.contractOpen
|
|
) {
|
|
terminalInput.focus();
|
|
}
|
|
|
|
if (event.keyCode === KEY.ENTER) {
|
|
event.preventDefault(); // Prevent newline from being entered in Script Editor
|
|
const command = getTerminalInput();
|
|
const dir = Terminal.currDir;
|
|
post(
|
|
"<span class='prompt'>[" +
|
|
(FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") +
|
|
Player.getCurrentServer().hostname +
|
|
` ~${dir}]></span> ${command}`,
|
|
);
|
|
|
|
if (command.length > 0) {
|
|
Terminal.resetTerminalInput(); // Clear input first
|
|
Terminal.executeCommands(command);
|
|
}
|
|
}
|
|
|
|
if (event.keyCode === KEY.C && event.ctrlKey) {
|
|
if (Engine._actionInProgress) {
|
|
// Cancel action
|
|
post("Cancelling...");
|
|
Engine._actionInProgress = false;
|
|
Terminal.finishAction(true);
|
|
} else if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
|
// Dont prevent default so it still copies
|
|
Terminal.resetTerminalInput(); // Clear Terminal
|
|
}
|
|
}
|
|
|
|
if (event.keyCode === KEY.L && event.ctrlKey) {
|
|
event.preventDefault();
|
|
Terminal.executeCommand("clear"); // Clear screen
|
|
}
|
|
|
|
// Ctrl p same as up arrow
|
|
// Ctrl n same as down arrow
|
|
|
|
if (
|
|
event.keyCode === KEY.UPARROW ||
|
|
(FconfSettings.ENABLE_BASH_HOTKEYS &&
|
|
event.keyCode === KEY.P &&
|
|
event.ctrlKey)
|
|
) {
|
|
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
|
event.preventDefault();
|
|
}
|
|
// Cycle through past commands
|
|
if (terminalInput == null) {
|
|
return;
|
|
}
|
|
var i = Terminal.commandHistoryIndex;
|
|
var len = Terminal.commandHistory.length;
|
|
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
if (i < 0 || i > len) {
|
|
Terminal.commandHistoryIndex = len;
|
|
}
|
|
|
|
if (i != 0) {
|
|
--Terminal.commandHistoryIndex;
|
|
}
|
|
var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex];
|
|
terminalInput.value = prevCommand;
|
|
setTimeoutRef(function () {
|
|
terminalInput.selectionStart = terminalInput.selectionEnd = 10000;
|
|
}, 10);
|
|
}
|
|
|
|
if (
|
|
event.keyCode === KEY.DOWNARROW ||
|
|
(FconfSettings.ENABLE_BASH_HOTKEYS &&
|
|
event.keyCode === KEY.M &&
|
|
event.ctrlKey)
|
|
) {
|
|
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
|
event.preventDefault();
|
|
}
|
|
// Cycle through past commands
|
|
if (terminalInput == null) {
|
|
return;
|
|
}
|
|
var i = Terminal.commandHistoryIndex;
|
|
var len = Terminal.commandHistory.length;
|
|
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
if (i < 0 || i > len) {
|
|
Terminal.commandHistoryIndex = len;
|
|
}
|
|
|
|
// Latest command, put nothing
|
|
if (i == len || i == len - 1) {
|
|
Terminal.commandHistoryIndex = len;
|
|
terminalInput.value = "";
|
|
} else {
|
|
++Terminal.commandHistoryIndex;
|
|
var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex];
|
|
terminalInput.value = prevCommand;
|
|
}
|
|
}
|
|
|
|
if (event.keyCode === KEY.TAB) {
|
|
event.preventDefault();
|
|
|
|
// Autocomplete
|
|
if (terminalInput == null) {
|
|
return;
|
|
}
|
|
let input = terminalInput.value;
|
|
if (input == "") {
|
|
return;
|
|
}
|
|
|
|
const semiColonIndex = input.lastIndexOf(";");
|
|
if (semiColonIndex !== -1) {
|
|
input = input.slice(semiColonIndex + 1);
|
|
}
|
|
|
|
input = input.trim();
|
|
input = input.replace(/\s\s+/g, " ");
|
|
|
|
const commandArray = input.split(" ");
|
|
let index = commandArray.length - 2;
|
|
if (index < -1) {
|
|
index = 0;
|
|
}
|
|
const allPos = determineAllPossibilitiesForTabCompletion(
|
|
Player,
|
|
input,
|
|
index,
|
|
Terminal.currDir,
|
|
);
|
|
if (allPos.length == 0) {
|
|
return;
|
|
}
|
|
|
|
let arg = "";
|
|
let command = "";
|
|
if (commandArray.length == 0) {
|
|
return;
|
|
}
|
|
if (commandArray.length == 1) {
|
|
command = commandArray[0];
|
|
} else if (commandArray.length == 2) {
|
|
command = commandArray[0];
|
|
arg = commandArray[1];
|
|
} else if (commandArray.length == 3) {
|
|
command = commandArray[0] + " " + commandArray[1];
|
|
arg = commandArray[2];
|
|
} else {
|
|
arg = commandArray.pop();
|
|
command = commandArray.join(" ");
|
|
}
|
|
|
|
tabCompletion(command, arg, allPos);
|
|
terminalInput.focus();
|
|
}
|
|
|
|
// Extra Bash Emulation Hotkeys, must be enabled through .fconf
|
|
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
|
if (event.keyCode === KEY.A && event.ctrlKey) {
|
|
event.preventDefault();
|
|
Terminal.moveTextCursor("home");
|
|
}
|
|
|
|
if (event.keyCode === KEY.E && event.ctrlKey) {
|
|
event.preventDefault();
|
|
Terminal.moveTextCursor("end");
|
|
}
|
|
|
|
if (event.keyCode === KEY.B && event.ctrlKey) {
|
|
event.preventDefault();
|
|
Terminal.moveTextCursor("prevchar");
|
|
}
|
|
|
|
if (event.keyCode === KEY.B && event.altKey) {
|
|
event.preventDefault();
|
|
Terminal.moveTextCursor("prevword");
|
|
}
|
|
|
|
if (event.keyCode === KEY.F && event.ctrlKey) {
|
|
event.preventDefault();
|
|
Terminal.moveTextCursor("nextchar");
|
|
}
|
|
|
|
if (event.keyCode === KEY.F && event.altKey) {
|
|
event.preventDefault();
|
|
Terminal.moveTextCursor("nextword");
|
|
}
|
|
|
|
if (
|
|
(event.keyCode === KEY.H || event.keyCode === KEY.D) &&
|
|
event.ctrlKey
|
|
) {
|
|
Terminal.modifyInput("backspace");
|
|
event.preventDefault();
|
|
}
|
|
|
|
// TODO AFTER THIS:
|
|
// alt + d deletes word after cursor
|
|
// ^w deletes word before cursor
|
|
// ^k clears line after cursor
|
|
// ^u clears line before cursor
|
|
}
|
|
}
|
|
});
|
|
|
|
// Keep terminal in focus
|
|
let terminalCtrlPressed = false,
|
|
shiftKeyPressed = false;
|
|
$(document).ready(function () {
|
|
if (routing.isOn(Page.Terminal)) {
|
|
$(".terminal-input").focus();
|
|
}
|
|
});
|
|
|
|
$(document).keydown(function (e) {
|
|
if (routing.isOn(Page.Terminal)) {
|
|
if (e.which == KEY.CTRL) {
|
|
terminalCtrlPressed = true;
|
|
} else if (e.shiftKey) {
|
|
shiftKeyPressed = true;
|
|
} else if (
|
|
terminalCtrlPressed ||
|
|
shiftKeyPressed ||
|
|
Terminal.contractOpen
|
|
) {
|
|
// Don't focus
|
|
} else {
|
|
var inputTextBox = document.getElementById("terminal-input-text-box");
|
|
if (inputTextBox != null) {
|
|
inputTextBox.focus();
|
|
}
|
|
|
|
terminalCtrlPressed = false;
|
|
shiftKeyPressed = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
$(document).keyup(function (e) {
|
|
if (routing.isOn(Page.Terminal)) {
|
|
if (e.which == KEY.CTRL) {
|
|
terminalCtrlPressed = false;
|
|
}
|
|
if (e.shiftKey) {
|
|
shiftKeyPressed = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
let Terminal = {
|
|
// Flags to determine whether the player is currently running a hack or an analyze
|
|
hackFlag: false,
|
|
backdoorFlag: false,
|
|
analyzeFlag: false,
|
|
actionStarted: false,
|
|
actionTime: 0,
|
|
|
|
commandHistory: [],
|
|
commandHistoryIndex: 0,
|
|
|
|
// True if a Coding Contract prompt is opened
|
|
contractOpen: false,
|
|
|
|
// Full Path of current directory
|
|
// Excludes the trailing forward slash
|
|
currDir: "/",
|
|
|
|
resetTerminalInput: function (keepInput = false) {
|
|
let input = "";
|
|
if (keepInput) {
|
|
input = getTerminalInput();
|
|
}
|
|
const dir = Terminal.currDir;
|
|
if (FconfSettings.WRAP_INPUT) {
|
|
document.getElementById("terminal-input-td").innerHTML =
|
|
`<div id='terminal-input-header' class='prompt'>[${
|
|
Player.getCurrentServer().hostname
|
|
} ~${dir}]$ </div>` +
|
|
`<textarea type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\" autocomplete="off" />`;
|
|
|
|
// Auto re-size the line element as it wraps
|
|
autosize(document.getElementById("terminal-input-text-box"));
|
|
} else {
|
|
document.getElementById("terminal-input-td").innerHTML =
|
|
`<div id='terminal-input-header' class='prompt'>[${
|
|
Player.getCurrentServer().hostname
|
|
} ~${dir}]$ </div>` +
|
|
`<input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\" autocomplete="off" />`;
|
|
}
|
|
const hdr = document.getElementById("terminal-input-header");
|
|
hdr.style.display = "inline";
|
|
|
|
const terminalInput = document.getElementById("terminal-input-text-box");
|
|
if (typeof terminalInput.selectionStart == "number") {
|
|
terminalInput.selectionStart = terminalInput.selectionEnd =
|
|
terminalInput.value.length;
|
|
} else if (typeof terminalInput.createTextRange != "undefined") {
|
|
terminalInput.focus();
|
|
var range = el.createTextRange();
|
|
range.collapse(false);
|
|
range.select();
|
|
}
|
|
},
|
|
|
|
modifyInput: function (mod) {
|
|
try {
|
|
var terminalInput = document.getElementById("terminal-input-text-box");
|
|
if (terminalInput == null) {
|
|
return;
|
|
}
|
|
terminalInput.focus();
|
|
|
|
var inputLength = terminalInput.value.length;
|
|
var start = terminalInput.selectionStart;
|
|
var inputText = terminalInput.value;
|
|
|
|
switch (mod.toLowerCase()) {
|
|
case "backspace":
|
|
if (start > 0 && start <= inputLength + 1) {
|
|
terminalInput.value =
|
|
inputText.substr(0, start - 1) + inputText.substr(start);
|
|
}
|
|
break;
|
|
case "deletewordbefore": // Delete rest of word before the cursor
|
|
for (var delStart = start - 1; delStart > 0; --delStart) {
|
|
if (inputText.charAt(delStart) === " ") {
|
|
terminalInput.value =
|
|
inputText.substr(0, delStart) + inputText.substr(start);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case "deletewordafter": // Delete rest of word after the cursor
|
|
for (
|
|
var delStart = start + 1;
|
|
delStart <= text.length + 1;
|
|
++delStart
|
|
) {
|
|
if (inputText.charAt(delStart) === " ") {
|
|
terminalInput.value =
|
|
inputText.substr(0, start) + inputText.substr(delStart);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case "clearafter": // Deletes everything after cursor
|
|
break;
|
|
case "clearbefore:": // Deleetes everything before cursor
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
console.error("Exception in Terminal.modifyInput: " + e);
|
|
}
|
|
},
|
|
|
|
moveTextCursor: function (loc) {
|
|
try {
|
|
var terminalInput = document.getElementById("terminal-input-text-box");
|
|
if (terminalInput == null) {
|
|
return;
|
|
}
|
|
terminalInput.focus();
|
|
|
|
var inputLength = terminalInput.value.length;
|
|
var start = terminalInput.selectionStart;
|
|
|
|
switch (loc.toLowerCase()) {
|
|
case "home":
|
|
terminalInput.setSelectionRange(0, 0);
|
|
break;
|
|
case "end":
|
|
terminalInput.setSelectionRange(inputLength, inputLength);
|
|
break;
|
|
case "prevchar":
|
|
if (start > 0) {
|
|
terminalInput.setSelectionRange(start - 1, start - 1);
|
|
}
|
|
break;
|
|
case "prevword":
|
|
for (var i = start - 2; i >= 0; --i) {
|
|
if (terminalInput.value.charAt(i) === " ") {
|
|
terminalInput.setSelectionRange(i + 1, i + 1);
|
|
return;
|
|
}
|
|
}
|
|
terminalInput.setSelectionRange(0, 0);
|
|
break;
|
|
case "nextchar":
|
|
terminalInput.setSelectionRange(start + 1, start + 1);
|
|
break;
|
|
case "nextword":
|
|
for (var i = start + 1; i <= inputLength; ++i) {
|
|
if (terminalInput.value.charAt(i) === " ") {
|
|
terminalInput.setSelectionRange(i, i);
|
|
return;
|
|
}
|
|
}
|
|
terminalInput.setSelectionRange(inputLength, inputLength);
|
|
break;
|
|
default:
|
|
console.warn("Invalid loc argument in Terminal.moveTextCursor()");
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
console.error("Exception in Terminal.moveTextCursor: " + e);
|
|
}
|
|
},
|
|
|
|
startHack: function () {
|
|
Terminal.hackFlag = true;
|
|
|
|
// Hacking through Terminal should be faster than hacking through a script
|
|
Terminal.actionTime =
|
|
calculateHackingTime(Player.getCurrentServer(), Player) / 4;
|
|
Terminal.startAction();
|
|
},
|
|
|
|
startBackdoor: function () {
|
|
Terminal.backdoorFlag = true;
|
|
|
|
// Backdoor should take the same amount of time as hack
|
|
Terminal.actionTime =
|
|
calculateHackingTime(Player.getCurrentServer(), Player) / 4;
|
|
Terminal.startAction();
|
|
},
|
|
|
|
startAnalyze: function () {
|
|
Terminal.analyzeFlag = true;
|
|
Terminal.actionTime = 1;
|
|
post("Analyzing system...");
|
|
Terminal.startAction();
|
|
},
|
|
|
|
startAction: function () {
|
|
Terminal.actionStarted = true;
|
|
|
|
hackProgressPost("Time left:");
|
|
hackProgressBarPost("[");
|
|
|
|
// Disable terminal
|
|
document.getElementById("terminal-input-td").innerHTML =
|
|
'<input type="text" class="terminal-input"/>';
|
|
$("input[class=terminal-input]").prop("disabled", true);
|
|
},
|
|
|
|
finishAction: function (cancelled = false) {
|
|
if (Terminal.hackFlag) {
|
|
Terminal.finishHack(cancelled);
|
|
} else if (Terminal.backdoorFlag) {
|
|
Terminal.finishBackdoor(cancelled);
|
|
} else if (Terminal.analyzeFlag) {
|
|
Terminal.finishAnalyze(cancelled);
|
|
}
|
|
|
|
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
|
|
$("#hack-progress-bar").attr("id", "old-hack-progress-bar");
|
|
$("#hack-progress").attr("id", "old-hack-progress");
|
|
Terminal.resetTerminalInput();
|
|
$("input[class=terminal-input]").prop("disabled", false);
|
|
},
|
|
|
|
// Complete the hack/analyze command
|
|
finishHack: function (cancelled = false) {
|
|
if (!cancelled) {
|
|
var server = Player.getCurrentServer();
|
|
|
|
// Calculate whether hack was successful
|
|
var hackChance = calculateHackingChance(server, Player);
|
|
var rand = Math.random();
|
|
var expGainedOnSuccess = calculateHackingExpGain(server, Player);
|
|
var expGainedOnFailure = expGainedOnSuccess / 4;
|
|
if (rand < hackChance) {
|
|
// Success!
|
|
if (
|
|
SpecialServerIps[SpecialServerNames.WorldDaemon] &&
|
|
SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip
|
|
) {
|
|
if (Player.bitNodeN == null) {
|
|
Player.bitNodeN = 1;
|
|
}
|
|
hackWorldDaemon(Player.bitNodeN);
|
|
Terminal.hackFlag = false;
|
|
return;
|
|
}
|
|
server.backdoorInstalled = true;
|
|
var moneyGained = calculatePercentMoneyHacked(server, Player);
|
|
moneyGained = Math.floor(server.moneyAvailable * moneyGained);
|
|
|
|
if (moneyGained <= 0) {
|
|
moneyGained = 0;
|
|
} // Safety check
|
|
|
|
server.moneyAvailable -= moneyGained;
|
|
Player.gainMoney(moneyGained);
|
|
Player.recordMoneySource(moneyGained, "hacking");
|
|
Player.gainHackingExp(expGainedOnSuccess);
|
|
Player.gainIntelligenceExp(
|
|
expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain,
|
|
);
|
|
|
|
server.fortify(CONSTANTS.ServerFortifyAmount);
|
|
|
|
postElement(
|
|
<>
|
|
Hack successful! Gained <Money money={moneyGained} /> and{" "}
|
|
{numeralWrapper.formatExp(expGainedOnSuccess)} hacking exp
|
|
</>,
|
|
);
|
|
} else {
|
|
// Failure
|
|
// Player only gains 25% exp for failure? TODO Can change this later to balance
|
|
Player.gainHackingExp(expGainedOnFailure);
|
|
post(
|
|
`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(
|
|
expGainedOnFailure,
|
|
)} hacking exp`,
|
|
);
|
|
}
|
|
}
|
|
Terminal.hackFlag = false;
|
|
},
|
|
|
|
finishBackdoor: function (cancelled = false) {
|
|
if (!cancelled) {
|
|
const server = Player.getCurrentServer();
|
|
if (
|
|
SpecialServerIps[SpecialServerNames.WorldDaemon] &&
|
|
SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip
|
|
) {
|
|
if (Player.bitNodeN == null) {
|
|
Player.bitNodeN = 1;
|
|
}
|
|
hackWorldDaemon(Player.bitNodeN);
|
|
Terminal.backdoorFlag = false;
|
|
return;
|
|
}
|
|
server.backdoorInstalled = true;
|
|
postElement(<>Backdoor successful!</>);
|
|
}
|
|
Terminal.backdoorFlag = false;
|
|
},
|
|
|
|
finishAnalyze: function (cancelled = false) {
|
|
if (!cancelled) {
|
|
let currServ = Player.getCurrentServer();
|
|
const isHacknet = currServ instanceof HacknetServer;
|
|
post(currServ.hostname + ": ");
|
|
const org = currServ.organizationName;
|
|
post("Organization name: " + (!isHacknet ? org : "Player"));
|
|
let hasAdminRights = (!isHacknet && currServ.hasAdminRights) || isHacknet;
|
|
post("Root Access: " + (hasAdminRights ? "YES" : "NO"));
|
|
const hackingSkill = currServ.requiredHackingSkill;
|
|
post("Required hacking skill: " + (!isHacknet ? hackingSkill : "N/A"));
|
|
const security = currServ.hackDifficulty;
|
|
post(
|
|
"Server security level: " +
|
|
(!isHacknet ? numeralWrapper.formatServerSecurity(security) : "N/A"),
|
|
);
|
|
const hackingChance = calculateHackingChance(currServ, Player);
|
|
post(
|
|
"Chance to hack: " +
|
|
(!isHacknet ? numeralWrapper.formatPercentage(hackingChance) : "N/A"),
|
|
);
|
|
const hackingTime = calculateHackingTime(currServ, Player) * 1000;
|
|
post(
|
|
"Time to hack: " +
|
|
(!isHacknet
|
|
? convertTimeMsToTimeElapsedString(hackingTime, true)
|
|
: "N/A"),
|
|
);
|
|
postElement(
|
|
<>
|
|
Total money available on server:{" "}
|
|
{!isHacknet ? <Money money={currServ.moneyAvailable} /> : "N/A"}
|
|
</>,
|
|
);
|
|
const numPort = currServ.numOpenPortsRequired;
|
|
post(
|
|
"Required number of open ports for NUKE: " +
|
|
(!isHacknet ? numPort : "N/A"),
|
|
);
|
|
post("SSH port: " + (currServ.sshPortOpen ? "Open" : "Closed"));
|
|
post("FTP port: " + (currServ.ftpPortOpen ? "Open" : "Closed"));
|
|
post("SMTP port: " + (currServ.smtpPortOpen ? "Open" : "Closed"));
|
|
post("HTTP port: " + (currServ.httpPortOpen ? "Open" : "Closed"));
|
|
post("SQL port: " + (currServ.sqlPortOpen ? "Open" : "Closed"));
|
|
}
|
|
Terminal.analyzeFlag = false;
|
|
},
|
|
|
|
executeCommands: function (commands) {
|
|
// Sanitize input
|
|
commands = commands.trim();
|
|
commands = commands.replace(/\s\s+/g, " "); // Replace all extra whitespace in command with a single space
|
|
|
|
// Handle Terminal History - multiple commands should be saved as one
|
|
if (
|
|
Terminal.commandHistory[Terminal.commandHistory.length - 1] != commands
|
|
) {
|
|
Terminal.commandHistory.push(commands);
|
|
if (Terminal.commandHistory.length > 50) {
|
|
Terminal.commandHistory.splice(0, 1);
|
|
}
|
|
}
|
|
Terminal.commandHistoryIndex = Terminal.commandHistory.length;
|
|
|
|
// Split commands and execute sequentially
|
|
commands = commands
|
|
.match(/(?:'[^']*'|"[^"]*"|[^;"])*/g)
|
|
.map(substituteAliases)
|
|
.map((c) => c.match(/(?:'[^']*'|"[^"]*"|[^;"])*/g))
|
|
.flat();
|
|
for (let i = 0; i < commands.length; i++) {
|
|
if (commands[i].match(/^\s*$/)) {
|
|
continue;
|
|
} // Don't run commands that only have whitespace
|
|
Terminal.executeCommand(commands[i].trim());
|
|
}
|
|
},
|
|
|
|
parseCommandArguments: function (command) {
|
|
// This will be used to keep track of whether we're in a quote. This is for situations
|
|
// like the alias command:
|
|
// alias run="run NUKE.exe"
|
|
// We want the run="run NUKE.exe" to be parsed as a single command, so this flag
|
|
// will keep track of whether we have a quote in
|
|
let inQuote = ``;
|
|
|
|
// Returns an array with the command and its arguments in each index
|
|
// Properly handles quotation marks (e.g. `run foo.script "the sun"` will return [run, foo.script, the sun])
|
|
const args = [];
|
|
let start = 0,
|
|
i = 0;
|
|
let prevChar = ""; // Previous character
|
|
while (i < command.length) {
|
|
let escaped = false; // Check for escaped quotation marks
|
|
if (i >= 1) {
|
|
prevChar = command.charAt(i - 1);
|
|
if (prevChar === "\\") {
|
|
escaped = true;
|
|
}
|
|
}
|
|
|
|
const c = command.charAt(i);
|
|
if (c === '"') {
|
|
// Double quotes
|
|
if (!escaped && prevChar === " ") {
|
|
const endQuote = command.indexOf('"', i + 1);
|
|
if (
|
|
endQuote !== -1 &&
|
|
(endQuote === command.length - 1 ||
|
|
command.charAt(endQuote + 1) === " ")
|
|
) {
|
|
args.push(command.substr(i + 1, endQuote - i - 1));
|
|
if (endQuote === command.length - 1) {
|
|
start = i = endQuote + 1;
|
|
} else {
|
|
start = i = endQuote + 2; // Skip the space
|
|
}
|
|
continue;
|
|
}
|
|
} else {
|
|
if (inQuote === ``) {
|
|
inQuote = `"`;
|
|
} else if (inQuote === `"`) {
|
|
inQuote = ``;
|
|
}
|
|
}
|
|
} else if (c === "'") {
|
|
// Single quotes, same thing as above
|
|
if (!escaped && prevChar === " ") {
|
|
const endQuote = command.indexOf("'", i + 1);
|
|
if (
|
|
endQuote !== -1 &&
|
|
(endQuote === command.length - 1 ||
|
|
command.charAt(endQuote + 1) === " ")
|
|
) {
|
|
args.push(command.substr(i + 1, endQuote - i - 1));
|
|
if (endQuote === command.length - 1) {
|
|
start = i = endQuote + 1;
|
|
} else {
|
|
start = i = endQuote + 2; // Skip the space
|
|
}
|
|
continue;
|
|
}
|
|
} else {
|
|
if (inQuote === ``) {
|
|
inQuote = `'`;
|
|
} else if (inQuote === `'`) {
|
|
inQuote = ``;
|
|
}
|
|
}
|
|
} else if (c === " " && inQuote === ``) {
|
|
let arg = command.substr(start, i - start);
|
|
|
|
// If this is a number, convert it from a string to number
|
|
if (isNumber(arg)) {
|
|
args.push(parseFloat(arg));
|
|
} else {
|
|
args.push(arg);
|
|
}
|
|
|
|
start = i + 1;
|
|
}
|
|
++i;
|
|
}
|
|
|
|
// Add the last argument
|
|
if (start !== i) {
|
|
let arg = command.substr(start, i - start);
|
|
|
|
// If this is a number, convert it from string to number
|
|
if (isNumber(arg)) {
|
|
args.push(parseFloat(arg));
|
|
} else {
|
|
args.push(arg);
|
|
}
|
|
}
|
|
|
|
return args;
|
|
},
|
|
|
|
executeCommand: function (command) {
|
|
if (Terminal.hackFlag || Terminal.backdoorFlag || Terminal.analyzeFlag) {
|
|
postError(
|
|
`Cannot execute command (${command}) while an action is in progress`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Allow usage of ./
|
|
if (command.startsWith("./")) {
|
|
command = "run " + command.slice(2);
|
|
}
|
|
|
|
// Only split the first space
|
|
var commandArray = Terminal.parseCommandArguments(command);
|
|
if (commandArray.length == 0) {
|
|
return;
|
|
}
|
|
|
|
/****************** Interactive Tutorial Terminal Commands ******************/
|
|
if (ITutorial.isRunning) {
|
|
var n00dlesServ = GetServerByHostname("n00dles");
|
|
if (n00dlesServ == null) {
|
|
throw new Error("Could not get n00dles server");
|
|
return;
|
|
}
|
|
|
|
switch (ITutorial.currStep) {
|
|
case iTutorialSteps.TerminalHelp:
|
|
if (commandArray.length === 1 && commandArray[0] == "help") {
|
|
post(TerminalHelpText);
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalLs:
|
|
if (commandArray.length === 1 && commandArray[0] == "ls") {
|
|
Terminal.executeListCommand(commandArray);
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalScan:
|
|
if (commandArray.length === 1 && commandArray[0] == "scan") {
|
|
Terminal.executeScanCommand(commandArray);
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalScanAnalyze1:
|
|
if (commandArray.length == 1 && commandArray[0] == "scan-analyze") {
|
|
Terminal.executeScanAnalyzeCommand(1);
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalScanAnalyze2:
|
|
if (
|
|
commandArray.length == 2 &&
|
|
commandArray[0] == "scan-analyze" &&
|
|
commandArray[1] === 2
|
|
) {
|
|
Terminal.executeScanAnalyzeCommand(2);
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalConnect:
|
|
if (commandArray.length == 2) {
|
|
if (
|
|
commandArray[0] == "connect" &&
|
|
(commandArray[1] == "n00dles" ||
|
|
commandArray[1] == n00dlesServ.ip)
|
|
) {
|
|
Player.getCurrentServer().isConnectedTo = false;
|
|
Player.currentServer = n00dlesServ.ip;
|
|
Player.getCurrentServer().isConnectedTo = true;
|
|
post("Connected to n00dles");
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Wrong command! Try again!");
|
|
return;
|
|
}
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalAnalyze:
|
|
if (commandArray.length === 1 && commandArray[0] === "analyze") {
|
|
if (commandArray.length !== 1) {
|
|
post("Incorrect usage of analyze command. Usage: analyze");
|
|
return;
|
|
}
|
|
Terminal.startAnalyze();
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalNuke:
|
|
if (
|
|
commandArray.length == 2 &&
|
|
commandArray[0] == "run" &&
|
|
commandArray[1] == "NUKE.exe"
|
|
) {
|
|
n00dlesServ.hasAdminRights = true;
|
|
post("NUKE successful! Gained root access to n00dles");
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalManualHack:
|
|
if (commandArray.length == 1 && commandArray[0] == "hack") {
|
|
Terminal.startHack();
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalCreateScript:
|
|
if (
|
|
commandArray.length == 2 &&
|
|
commandArray[0] == "nano" &&
|
|
commandArray[1] == "n00dles.script"
|
|
) {
|
|
Engine.loadScriptEditorContent("n00dles.script", "");
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalFree:
|
|
if (commandArray.length == 1 && commandArray[0] == "free") {
|
|
Terminal.executeFreeCommand(commandArray);
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.TerminalRunScript:
|
|
if (
|
|
commandArray.length == 2 &&
|
|
commandArray[0] == "run" &&
|
|
commandArray[1] == "n00dles.script"
|
|
) {
|
|
Terminal.runScript(commandArray);
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
case iTutorialSteps.ActiveScriptsToTerminal:
|
|
if (
|
|
commandArray.length == 2 &&
|
|
commandArray[0] == "tail" &&
|
|
commandArray[1] == "n00dles.script"
|
|
) {
|
|
// Check that the script exists on this machine
|
|
var runningScript = findRunningScript(
|
|
"n00dles.script",
|
|
[],
|
|
Player.getCurrentServer(),
|
|
);
|
|
if (runningScript == null) {
|
|
post("Error: No such script exists");
|
|
return;
|
|
}
|
|
logBoxCreate(runningScript);
|
|
iTutorialNextStep();
|
|
} else {
|
|
post("Bad command. Please follow the tutorial");
|
|
}
|
|
break;
|
|
default:
|
|
post(
|
|
"Please follow the tutorial, or click 'Exit Tutorial' if you'd like to skip it",
|
|
);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/****************** END INTERACTIVE TUTORIAL ******************/
|
|
|
|
/* Command parser */
|
|
var s = Player.getCurrentServer();
|
|
switch (commandArray[0].toLowerCase()) {
|
|
case "alias":
|
|
if (commandArray.length === 1) {
|
|
printAliases();
|
|
return;
|
|
}
|
|
if (commandArray.length === 2) {
|
|
if (parseAliasDeclaration(commandArray[1])) {
|
|
post(`Set alias ${commandArray[1]}`);
|
|
return;
|
|
}
|
|
}
|
|
if (commandArray.length === 3) {
|
|
if (commandArray[1] === "-g") {
|
|
if (parseAliasDeclaration(commandArray[2], true)) {
|
|
post(`Set global alias ${commandArray[2]}`);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
postError(
|
|
'Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]',
|
|
);
|
|
break;
|
|
case "analyze":
|
|
if (commandArray.length !== 1) {
|
|
post("Incorrect usage of analyze command. Usage: analyze");
|
|
return;
|
|
}
|
|
Terminal.startAnalyze();
|
|
break;
|
|
case "backdoor":
|
|
if (commandArray.length !== 1) {
|
|
post("Incorrect usage of backdoor command. Usage: backdoor");
|
|
return;
|
|
}
|
|
|
|
if (s.purchasedByPlayer) {
|
|
postError(
|
|
"Cannot use backdoor on your own machines! You are currently connected to your home PC or one of your purchased servers",
|
|
);
|
|
} else if (!s.hasAdminRights) {
|
|
postError(
|
|
"You do not have admin rights for this machine! Cannot backdoor",
|
|
);
|
|
} else if (s.requiredHackingSkill > Player.hacking_skill) {
|
|
postError(
|
|
"Your hacking skill is not high enough to use backdoor on this machine. Try analyzing the machine to determine the required hacking skill",
|
|
);
|
|
} else if (s instanceof HacknetServer) {
|
|
postError("Cannot use backdoor on this type of Server");
|
|
} else {
|
|
Terminal.startBackdoor();
|
|
}
|
|
break;
|
|
case "buy":
|
|
if (SpecialServerIps.hasOwnProperty("Darkweb Server")) {
|
|
executeDarkwebTerminalCommand(commandArray);
|
|
} else {
|
|
postError(
|
|
"You need to be able to connect to the Dark Web to use the buy command. (Maybe there's a TOR router you can buy somewhere)",
|
|
);
|
|
}
|
|
break;
|
|
case "cat": {
|
|
try {
|
|
if (commandArray.length !== 2) {
|
|
postError("Incorrect usage of cat command. Usage: cat [file]");
|
|
return;
|
|
}
|
|
const filename = Terminal.getFilepath(commandArray[1]);
|
|
if (
|
|
!filename.endsWith(".msg") &&
|
|
!filename.endsWith(".lit") &&
|
|
!filename.endsWith(".txt")
|
|
) {
|
|
postError(
|
|
"Only .msg, .txt, and .lit files are viewable with cat (filename must end with .msg, .txt, or .lit)",
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (filename.endsWith(".msg") || filename.endsWith(".lit")) {
|
|
for (let i = 0; i < s.messages.length; ++i) {
|
|
if (filename.endsWith(".lit") && s.messages[i] === filename) {
|
|
showLiterature(s.messages[i]);
|
|
return;
|
|
} else if (
|
|
filename.endsWith(".msg") &&
|
|
s.messages[i].filename === filename
|
|
) {
|
|
showMessage(s.messages[i]);
|
|
return;
|
|
}
|
|
}
|
|
} else if (filename.endsWith(".txt")) {
|
|
const txt = Terminal.getTextFile(filename);
|
|
if (txt != null) {
|
|
txt.show();
|
|
return;
|
|
}
|
|
}
|
|
|
|
postError(`No such file ${filename}`);
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
break;
|
|
}
|
|
case "cd": {
|
|
if (commandArray.length > 2) {
|
|
postError("Incorrect number of arguments. Usage: cd [dir]");
|
|
} else {
|
|
let dir = commandArray.length === 2 ? commandArray[1] : "/";
|
|
|
|
let evaledDir;
|
|
if (dir === "/") {
|
|
evaledDir = "/";
|
|
} else {
|
|
// Ignore trailing slashes
|
|
dir = removeTrailingSlash(dir);
|
|
|
|
evaledDir = evaluateDirectoryPath(dir, Terminal.currDir);
|
|
if (evaledDir == null || evaledDir === "") {
|
|
postError("Invalid path. Failed to change directories");
|
|
return;
|
|
}
|
|
|
|
const server = Player.getCurrentServer();
|
|
if (
|
|
!server.scripts.some((script) =>
|
|
script.filename.startsWith(evaledDir),
|
|
) &&
|
|
!server.textFiles.some((file) => file.fn.startsWith(evaledDir))
|
|
) {
|
|
postError("Invalid path. Failed to change directories");
|
|
return;
|
|
}
|
|
}
|
|
|
|
Terminal.currDir = evaledDir;
|
|
|
|
// Reset input to update current directory on UI
|
|
Terminal.resetTerminalInput();
|
|
}
|
|
break;
|
|
}
|
|
case "check": {
|
|
try {
|
|
if (commandArray.length < 2) {
|
|
postError(
|
|
"Incorrect number of arguments. Usage: check [script] [arg1] [arg2]...",
|
|
);
|
|
} else {
|
|
const scriptName = Terminal.getFilepath(commandArray[1]);
|
|
// Can only tail script files
|
|
if (!isScriptFilename(scriptName)) {
|
|
postError(
|
|
"tail can only be called on .script files (filename must end with .script)",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Get args
|
|
let args = [];
|
|
for (var i = 2; i < commandArray.length; ++i) {
|
|
args.push(commandArray[i]);
|
|
}
|
|
|
|
// Check that the script exists on this machine
|
|
var runningScript = findRunningScript(scriptName, args, s);
|
|
if (runningScript == null) {
|
|
postError("No such script exists");
|
|
return;
|
|
}
|
|
runningScript.displayLog();
|
|
}
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "clear":
|
|
case "cls":
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of clear/cls command. Usage: clear/cls");
|
|
return;
|
|
}
|
|
$("#terminal tr:not(:last)").remove();
|
|
postNetburnerText();
|
|
break;
|
|
case "connect": {
|
|
// Disconnect from current server in terminal and connect to new one
|
|
if (commandArray.length !== 2) {
|
|
postError(
|
|
"Incorrect usage of connect command. Usage: connect [ip/hostname]",
|
|
);
|
|
return;
|
|
}
|
|
|
|
let ip = commandArray[1];
|
|
|
|
for (let i = 0; i < s.serversOnNetwork.length; i++) {
|
|
if (
|
|
getServerOnNetwork(s, i).ip == ip ||
|
|
getServerOnNetwork(s, i).hostname == ip
|
|
) {
|
|
Terminal.connectToServer(ip);
|
|
return;
|
|
}
|
|
}
|
|
|
|
postError("Host not found");
|
|
break;
|
|
}
|
|
case "download": {
|
|
try {
|
|
if (commandArray.length !== 2) {
|
|
postError(
|
|
"Incorrect usage of download command. Usage: download [script/text file]",
|
|
);
|
|
return;
|
|
}
|
|
const fn = commandArray[1];
|
|
if (fn === "*" || fn === "*.script" || fn === "*.txt") {
|
|
// Download all scripts as a zip
|
|
var zip = new JSZip();
|
|
if (fn === "*" || fn === "*.script") {
|
|
for (var i = 0; i < s.scripts.length; ++i) {
|
|
var file = new Blob([s.scripts[i].code], {
|
|
type: "text/plain",
|
|
});
|
|
zip.file(s.scripts[i].filename + ".js", file);
|
|
}
|
|
}
|
|
if (fn === "*" || fn === "*.txt") {
|
|
for (var i = 0; i < s.textFiles.length; ++i) {
|
|
var file = new Blob([s.textFiles[i].text], {
|
|
type: "text/plain",
|
|
});
|
|
zip.file(s.textFiles[i].fn, file);
|
|
}
|
|
}
|
|
|
|
let zipFn;
|
|
switch (fn) {
|
|
case "*.script":
|
|
zipFn = "bitburnerScripts.zip";
|
|
break;
|
|
case "*.txt":
|
|
zipFn = "bitburnerTexts.zip";
|
|
break;
|
|
default:
|
|
zipFn = "bitburnerFiles.zip";
|
|
break;
|
|
}
|
|
|
|
zip.generateAsync({ type: "blob" }).then(function (content) {
|
|
FileSaver.saveAs(content, zipFn);
|
|
});
|
|
return;
|
|
} else if (isScriptFilename(fn)) {
|
|
// Download a single script
|
|
const script = Terminal.getScript(fn);
|
|
if (script != null) {
|
|
return script.download();
|
|
}
|
|
} else if (fn.endsWith(".txt")) {
|
|
// Download a single text file
|
|
const txt = Terminal.getTextFile(fn);
|
|
if (txt != null) {
|
|
return txt.download();
|
|
}
|
|
} else {
|
|
postError(`Cannot download this filetype`);
|
|
return;
|
|
}
|
|
postError(`${fn} does not exist`);
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
break;
|
|
}
|
|
case "expr": {
|
|
if (commandArray.length <= 1) {
|
|
postError(
|
|
"Incorrect usage of expr command. Usage: expr [math expression]",
|
|
);
|
|
return;
|
|
}
|
|
let expr = commandArray.slice(1).join("");
|
|
|
|
// Sanitize the math expression
|
|
let sanitizedExpr = expr
|
|
.replace(/s+/g, "")
|
|
.replace(/[^-()\d/*+.]/g, "");
|
|
let result;
|
|
try {
|
|
result = eval(sanitizedExpr);
|
|
} catch (e) {
|
|
postError(`Could not evaluate expression: ${sanitizedExpr}`);
|
|
return;
|
|
}
|
|
post(result);
|
|
break;
|
|
}
|
|
case "free":
|
|
Terminal.executeFreeCommand(commandArray);
|
|
break;
|
|
case "hack": {
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of hack command. Usage: hack");
|
|
return;
|
|
}
|
|
// Hack the current PC (usually for money)
|
|
// You can't hack your home pc or servers you purchased
|
|
if (s.purchasedByPlayer) {
|
|
postError(
|
|
"Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers",
|
|
);
|
|
} else if (s.hasAdminRights == false) {
|
|
postError(
|
|
"You do not have admin rights for this machine! Cannot hack",
|
|
);
|
|
} else if (s.requiredHackingSkill > Player.hacking_skill) {
|
|
postError(
|
|
"Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill",
|
|
);
|
|
} else if (s instanceof HacknetServer) {
|
|
postError("Cannot hack this type of Server");
|
|
} else {
|
|
Terminal.startHack();
|
|
}
|
|
break;
|
|
}
|
|
case "help":
|
|
if (commandArray.length !== 1 && commandArray.length !== 2) {
|
|
postError("Incorrect usage of help command. Usage: help");
|
|
return;
|
|
}
|
|
if (commandArray.length === 1) {
|
|
post(TerminalHelpText);
|
|
} else {
|
|
var cmd = commandArray[1];
|
|
var txt = HelpTexts[cmd];
|
|
if (txt == null) {
|
|
postError("No help topics match '" + cmd + "'");
|
|
return;
|
|
}
|
|
post(txt);
|
|
}
|
|
break;
|
|
case "home":
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of home command. Usage: home");
|
|
return;
|
|
}
|
|
Player.getCurrentServer().isConnectedTo = false;
|
|
Player.currentServer = Player.getHomeComputer().ip;
|
|
Player.getCurrentServer().isConnectedTo = true;
|
|
post("Connected to home");
|
|
Terminal.currDir = "/";
|
|
Terminal.resetTerminalInput();
|
|
break;
|
|
case "hostname":
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of hostname command. Usage: hostname");
|
|
return;
|
|
}
|
|
post(Player.getCurrentServer().hostname);
|
|
break;
|
|
case "ifconfig":
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of ifconfig command. Usage: ifconfig");
|
|
return;
|
|
}
|
|
post(Player.getCurrentServer().ip);
|
|
break;
|
|
case "kill": {
|
|
Terminal.executeKillCommand(commandArray);
|
|
break;
|
|
}
|
|
case "killall": {
|
|
for (let i = s.runningScripts.length - 1; i >= 0; --i) {
|
|
killWorkerScript(s.runningScripts[i], s.ip, false);
|
|
}
|
|
WorkerScriptStartStopEventEmitter.emitEvent();
|
|
post("Killing all running scripts");
|
|
break;
|
|
}
|
|
case "ls": {
|
|
Terminal.executeListCommand(commandArray);
|
|
break;
|
|
}
|
|
case "lscpu": {
|
|
post(Player.getCurrentServer().cpuCores + " Core(s)");
|
|
break;
|
|
}
|
|
case "mem": {
|
|
Terminal.executeMemCommand(commandArray);
|
|
break;
|
|
}
|
|
case "mv": {
|
|
if (commandArray.length !== 3) {
|
|
postError(`Incorrect number of arguments. Usage: mv [src] [dest]`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const source = commandArray[1];
|
|
const dest = commandArray[2];
|
|
|
|
if (!isScriptFilename(source) && !source.endsWith(".txt")) {
|
|
postError(`'mv' can only be used on scripts and text files (.txt)`);
|
|
return;
|
|
}
|
|
|
|
const srcFile = Terminal.getFile(source);
|
|
if (srcFile == null) {
|
|
postError(`Source file ${source} does not exist`);
|
|
return;
|
|
}
|
|
|
|
const sourcePath = Terminal.getFilepath(source);
|
|
const destPath = Terminal.getFilepath(dest);
|
|
|
|
const destFile = Terminal.getFile(dest);
|
|
|
|
// 'mv' command only works on scripts and txt files.
|
|
// Also, you can't convert between different file types
|
|
if (isScriptFilename(source)) {
|
|
if (!isScriptFilename(dest)) {
|
|
postError(`Source and destination files must have the same type`);
|
|
return;
|
|
}
|
|
|
|
// Command doesnt work if script is running
|
|
if (s.isRunning(sourcePath)) {
|
|
postError(`Cannot use 'mv' on a script that is running`);
|
|
return;
|
|
}
|
|
|
|
if (destFile != null) {
|
|
// Already exists, will be overwritten, so we'll delete it
|
|
const status = s.removeFile(destPath);
|
|
if (!status.res) {
|
|
postError(
|
|
`Something went wrong...please contact game dev (probably a bug)`,
|
|
);
|
|
return;
|
|
} else {
|
|
post("Warning: The destination file was overwritten");
|
|
}
|
|
}
|
|
|
|
srcFile.filename = destPath;
|
|
} else if (source.endsWith(".txt")) {
|
|
if (!dest.endsWith(".txt")) {
|
|
postError(`Source and destination files must have the same type`);
|
|
return;
|
|
}
|
|
|
|
if (destFile != null) {
|
|
// Already exists, will be overwritten, so we'll delete it
|
|
const status = s.removeFile(destPath);
|
|
if (!status.res) {
|
|
postError(
|
|
`Something went wrong...please contact game dev (probably a bug)`,
|
|
);
|
|
return;
|
|
} else {
|
|
post("Warning: The destination file was overwritten");
|
|
}
|
|
}
|
|
|
|
srcFile.fn = destPath;
|
|
}
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "nano":
|
|
Terminal.executeNanoCommand(commandArray);
|
|
break;
|
|
case "ps":
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of ps command. Usage: ps");
|
|
return;
|
|
}
|
|
for (let i = 0; i < s.runningScripts.length; i++) {
|
|
let rsObj = s.runningScripts[i];
|
|
let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`;
|
|
for (let j = 0; j < rsObj.args.length; ++j) {
|
|
res += " " + rsObj.args[j].toString();
|
|
}
|
|
post(res);
|
|
}
|
|
break;
|
|
case "rm": {
|
|
if (commandArray.length !== 2) {
|
|
postError(
|
|
"Incorrect number of arguments. Usage: rm [program/script]",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Check programs
|
|
let delTarget, status;
|
|
try {
|
|
delTarget = Terminal.getFilepath(commandArray[1]);
|
|
status = s.removeFile(delTarget);
|
|
} catch (err) {
|
|
status = {
|
|
res: false,
|
|
msg: "No such file exists",
|
|
};
|
|
}
|
|
|
|
if (!status.res) {
|
|
postError(status.msg);
|
|
}
|
|
break;
|
|
}
|
|
case "run":
|
|
// Run a program or a script
|
|
if (commandArray.length < 2) {
|
|
postError(
|
|
"Incorrect number of arguments. Usage: run [program/script] [-t] [num threads] [arg1] [arg2]...",
|
|
);
|
|
} else {
|
|
var executableName = commandArray[1];
|
|
|
|
// Secret Music player!
|
|
if (executableName === "musicplayer") {
|
|
post(
|
|
'<iframe src="https://open.spotify.com/embed/user/danielyxie/playlist/1ORnnL6YNvXOracUaUV2kh" width="300" height="380" frameborder="0" allowtransparency="true"></iframe>',
|
|
false,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Check if its a script or just a program/executable
|
|
if (isScriptFilename(executableName)) {
|
|
Terminal.runScript(commandArray);
|
|
} else if (executableName.endsWith(".cct")) {
|
|
Terminal.runContract(executableName);
|
|
} else {
|
|
Terminal.runProgram(commandArray);
|
|
}
|
|
}
|
|
break;
|
|
case "scan":
|
|
Terminal.executeScanCommand(commandArray);
|
|
break;
|
|
case "scan-analyze":
|
|
if (commandArray.length === 1) {
|
|
Terminal.executeScanAnalyzeCommand(1);
|
|
} else {
|
|
// # of args must be 2 or 3
|
|
if (commandArray.length > 3) {
|
|
postError(
|
|
"Incorrect usage of scan-analyze command. usage: scan-analyze [depth]",
|
|
);
|
|
return;
|
|
}
|
|
let all = false;
|
|
if (commandArray.length === 3 && commandArray[2] === "-a") {
|
|
all = true;
|
|
}
|
|
|
|
let depth = parseInt(commandArray[1]);
|
|
|
|
if (isNaN(depth) || depth < 0) {
|
|
postError(
|
|
"Incorrect usage of scan-analyze command. depth argument must be positive numeric",
|
|
);
|
|
return;
|
|
}
|
|
if (
|
|
depth > 3 &&
|
|
!Player.hasProgram(Programs.DeepscanV1.name) &&
|
|
!Player.hasProgram(Programs.DeepscanV2.name)
|
|
) {
|
|
postError(
|
|
"You cannot scan-analyze with that high of a depth. Maximum depth is 3",
|
|
);
|
|
return;
|
|
} else if (
|
|
depth > 5 &&
|
|
!Player.hasProgram(Programs.DeepscanV2.name)
|
|
) {
|
|
postError(
|
|
"You cannot scan-analyze with that high of a depth. Maximum depth is 5",
|
|
);
|
|
return;
|
|
} else if (depth > 10) {
|
|
postError(
|
|
"You cannot scan-analyze with that high of a depth. Maximum depth is 10",
|
|
);
|
|
return;
|
|
}
|
|
Terminal.executeScanAnalyzeCommand(depth, all);
|
|
}
|
|
break;
|
|
/* eslint-disable no-case-declarations */
|
|
case "scp":
|
|
Terminal.executeScpCommand(commandArray);
|
|
break;
|
|
/* eslint-enable no-case-declarations */
|
|
case "sudov":
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect number of arguments. Usage: sudov");
|
|
return;
|
|
}
|
|
|
|
if (s.hasAdminRights) {
|
|
post("You have ROOT access to this machine");
|
|
} else {
|
|
post("You do NOT have root access to this machine");
|
|
}
|
|
break;
|
|
case "tail": {
|
|
try {
|
|
if (commandArray.length < 2) {
|
|
postError(
|
|
"Incorrect number of arguments. Usage: tail [script] [arg1] [arg2]...",
|
|
);
|
|
} else if (typeof commandArray[1] === "string") {
|
|
const scriptName = Terminal.getFilepath(commandArray[1]);
|
|
if (!isScriptFilename(scriptName)) {
|
|
postError(
|
|
"tail can only be called on .script, .ns, .js files, or by pid",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Get script arguments
|
|
const args = [];
|
|
for (let i = 2; i < commandArray.length; ++i) {
|
|
args.push(commandArray[i]);
|
|
}
|
|
|
|
// go over all the running scripts. If there's a perfect
|
|
// match, use it!
|
|
for (var i = 0; i < s.runningScripts.length; ++i) {
|
|
if (
|
|
s.runningScripts[i].filename === scriptName &&
|
|
compareArrays(s.runningScripts[i].args, args)
|
|
) {
|
|
logBoxCreate(s.runningScripts[i]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Find all scripts that are potential candidates.
|
|
const candidates = [];
|
|
for (var i = 0; i < s.runningScripts.length; ++i) {
|
|
// only scripts that have more arguments (equal arguments is already caught)
|
|
if (s.runningScripts[i].args.length < args.length) continue;
|
|
// make a smaller copy of the args.
|
|
const args2 = s.runningScripts[i].args.slice(0, args.length);
|
|
if (
|
|
s.runningScripts[i].filename === scriptName &&
|
|
compareArrays(args2, args)
|
|
) {
|
|
candidates.push(s.runningScripts[i]);
|
|
}
|
|
}
|
|
|
|
// If there's only 1 possible choice, use that.
|
|
if (candidates.length === 1) {
|
|
logBoxCreate(candidates[0]);
|
|
return;
|
|
}
|
|
|
|
// otherwise lists all possible conflicting choices.
|
|
if (candidates.length > 1) {
|
|
postError("Found several potential candidates:");
|
|
for (const candidate of candidates)
|
|
postError(`${candidate.filename} ${candidate.args.join(" ")}`);
|
|
postError("Script arguments need to be specified.");
|
|
return;
|
|
}
|
|
|
|
// if there's no candidate then we just don't know.
|
|
postError("No such script exists.");
|
|
} else {
|
|
const runningScript = findRunningScriptByPid(
|
|
commandArray[1],
|
|
Player.getCurrentServer(),
|
|
);
|
|
if (runningScript == null) {
|
|
postError("No such script exists");
|
|
return;
|
|
}
|
|
logBoxCreate(runningScript);
|
|
}
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "theme": {
|
|
let args = commandArray.slice(1);
|
|
if (args.length !== 1 && args.length !== 3) {
|
|
postError("Incorrect number of arguments.");
|
|
postError(
|
|
"Usage: theme [default|muted|solarized] | #[background color hex] #[text color hex] #[highlight color hex]",
|
|
);
|
|
} else if (args.length === 1) {
|
|
var themeName = args[0];
|
|
if (themeName == "default") {
|
|
document.body.style.setProperty("--my-highlight-color", "#ffffff");
|
|
document.body.style.setProperty("--my-font-color", "#66ff33");
|
|
document.body.style.setProperty("--my-background-color", "#000000");
|
|
document.body.style.setProperty("--my-prompt-color", "#f92672");
|
|
} else if (themeName == "muted") {
|
|
document.body.style.setProperty("--my-highlight-color", "#ffffff");
|
|
document.body.style.setProperty("--my-font-color", "#66ff33");
|
|
document.body.style.setProperty("--my-background-color", "#252527");
|
|
} else if (themeName == "solarized") {
|
|
document.body.style.setProperty("--my-highlight-color", "#6c71c4");
|
|
document.body.style.setProperty("--my-font-color", "#839496");
|
|
document.body.style.setProperty("--my-background-color", "#002b36");
|
|
} else {
|
|
return postError("Theme not found");
|
|
}
|
|
FconfSettings.THEME_HIGHLIGHT_COLOR =
|
|
document.body.style.getPropertyValue("--my-highlight-color");
|
|
FconfSettings.THEME_FONT_COLOR =
|
|
document.body.style.getPropertyValue("--my-font-color");
|
|
FconfSettings.THEME_BACKGROUND_COLOR =
|
|
document.body.style.getPropertyValue("--my-background-color");
|
|
FconfSettings.THEME_PROMPT_COLOR =
|
|
document.body.style.getPropertyValue("--my-prompt-color");
|
|
} else {
|
|
var inputBackgroundHex = args[0];
|
|
var inputTextHex = args[1];
|
|
var inputHighlightHex = args[2];
|
|
if (
|
|
/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(inputBackgroundHex) &&
|
|
/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(inputTextHex) &&
|
|
/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(inputHighlightHex)
|
|
) {
|
|
document.body.style.setProperty(
|
|
"--my-highlight-color",
|
|
inputHighlightHex,
|
|
);
|
|
document.body.style.setProperty("--my-font-color", inputTextHex);
|
|
document.body.style.setProperty(
|
|
"--my-background-color",
|
|
inputBackgroundHex,
|
|
);
|
|
FconfSettings.THEME_HIGHLIGHT_COLOR =
|
|
document.body.style.getPropertyValue("--my-highlight-color");
|
|
FconfSettings.THEME_FONT_COLOR =
|
|
document.body.style.getPropertyValue("--my-font-color");
|
|
FconfSettings.THEME_BACKGROUND_COLOR =
|
|
document.body.style.getPropertyValue("--my-background-color");
|
|
} else {
|
|
return postError("Invalid Hex Input for theme");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "top": {
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of top command. Usage: top");
|
|
return;
|
|
}
|
|
|
|
// Headers
|
|
const scriptWidth = 40;
|
|
const pidWidth = 10;
|
|
const threadsWidth = 16;
|
|
|
|
const scriptTxt = "Script";
|
|
const pidTxt = "PID";
|
|
const threadsTxt = "Threads";
|
|
const ramTxt = "RAM Usage";
|
|
|
|
const spacesAfterScriptTxt = " ".repeat(scriptWidth - scriptTxt.length);
|
|
const spacesAfterPidTxt = " ".repeat(pidWidth - pidTxt.length);
|
|
const spacesAfterThreadsTxt = " ".repeat(
|
|
threadsWidth - threadsTxt.length,
|
|
);
|
|
|
|
const headers = `${scriptTxt}${spacesAfterScriptTxt}${pidTxt}${spacesAfterPidTxt}${threadsTxt}${spacesAfterThreadsTxt}${ramTxt}`;
|
|
|
|
post(headers);
|
|
|
|
let currRunningScripts = s.runningScripts;
|
|
// Iterate through scripts on current server
|
|
for (let i = 0; i < currRunningScripts.length; i++) {
|
|
let script = currRunningScripts[i];
|
|
|
|
// Calculate name padding
|
|
const numSpacesScript = Math.max(
|
|
0,
|
|
scriptWidth - script.filename.length,
|
|
);
|
|
const spacesScript = " ".repeat(numSpacesScript);
|
|
|
|
// Calculate PID padding
|
|
const numSpacesPid = Math.max(0, pidWidth - (script.pid + "").length);
|
|
const spacesPid = " ".repeat(numSpacesPid);
|
|
|
|
// Calculate thread padding
|
|
const numSpacesThread = Math.max(
|
|
0,
|
|
threadsWidth - (script.threads + "").length,
|
|
);
|
|
const spacesThread = " ".repeat(numSpacesThread);
|
|
|
|
// Calculate and transform RAM usage
|
|
const ramUsage = numeralWrapper.formatRAM(
|
|
getRamUsageFromRunningScript(script) * script.threads,
|
|
);
|
|
|
|
const entry = [
|
|
script.filename,
|
|
spacesScript,
|
|
script.pid,
|
|
spacesPid,
|
|
script.threads,
|
|
spacesThread,
|
|
ramUsage,
|
|
].join("");
|
|
post(entry);
|
|
}
|
|
break;
|
|
}
|
|
case "unalias": {
|
|
if (commandArray.length !== 2) {
|
|
postError("Incorrect usage of unalias name. Usage: unalias [alias]");
|
|
return;
|
|
} else {
|
|
if (removeAlias(commandArray[1])) {
|
|
post(`Removed alias ${commandArray[1]}`);
|
|
} else {
|
|
postError(`No such alias exists: ${commandArray[1]}`);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "wget": {
|
|
if (commandArray.length !== 3) {
|
|
postError(
|
|
"Incorrect usage of wget command. Usage: wget [url] [target file]",
|
|
);
|
|
return;
|
|
}
|
|
|
|
let url = commandArray[1];
|
|
let target = Terminal.getFilepath(commandArray[2]);
|
|
if (!isScriptFilename(target) && !target.endsWith(".txt")) {
|
|
return post(
|
|
`wget failed: Invalid target file. Target file must be script or text file`,
|
|
);
|
|
}
|
|
$.get(
|
|
url,
|
|
function (data) {
|
|
let res;
|
|
if (isScriptFilename(target)) {
|
|
res = s.writeToScriptFile(target, data);
|
|
} else {
|
|
res = s.writeToTextFile(target, data);
|
|
}
|
|
if (!res.success) {
|
|
return post("wget failed");
|
|
}
|
|
if (res.overwritten) {
|
|
return post(
|
|
`wget successfully retrieved content and overwrote ${target}`,
|
|
);
|
|
}
|
|
return post(
|
|
`wget successfully retrieved content to new file ${target}`,
|
|
);
|
|
},
|
|
"text",
|
|
).fail(function (e) {
|
|
return postError("wget failed: " + JSON.stringify(e));
|
|
});
|
|
break;
|
|
}
|
|
default:
|
|
postError(`Command ${commandArray[0]} not found`);
|
|
}
|
|
},
|
|
|
|
connectToServer: function (ip) {
|
|
var serv = getServer(ip);
|
|
if (serv == null) {
|
|
post("Invalid server. Connection failed.");
|
|
return;
|
|
}
|
|
Player.getCurrentServer().isConnectedTo = false;
|
|
Player.currentServer = serv.ip;
|
|
Player.getCurrentServer().isConnectedTo = true;
|
|
post("Connected to " + serv.hostname);
|
|
Terminal.currDir = "/";
|
|
if (Player.getCurrentServer().hostname == "darkweb") {
|
|
checkIfConnectedToDarkweb(); // Posts a 'help' message if connecting to dark web
|
|
}
|
|
Terminal.resetTerminalInput();
|
|
},
|
|
|
|
executeFreeCommand: function (commandArray) {
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of free command. Usage: free");
|
|
return;
|
|
}
|
|
const ram = numeralWrapper.formatRAM(Player.getCurrentServer().maxRam);
|
|
const used = numeralWrapper.formatRAM(Player.getCurrentServer().ramUsed);
|
|
const avail = numeralWrapper.formatRAM(
|
|
Player.getCurrentServer().maxRam - Player.getCurrentServer().ramUsed,
|
|
);
|
|
const maxLength = Math.max(ram.length, Math.max(used.length, avail.length));
|
|
const usedPercent = numeralWrapper.formatPercentage(
|
|
Player.getCurrentServer().ramUsed / Player.getCurrentServer().maxRam,
|
|
);
|
|
|
|
post(`Total: ${" ".repeat(maxLength - ram.length)}${ram}`);
|
|
post(
|
|
`Used: ${" ".repeat(
|
|
maxLength - used.length,
|
|
)}${used} (${usedPercent})`,
|
|
);
|
|
post(`Available: ${" ".repeat(maxLength - avail.length)}${avail}`);
|
|
},
|
|
|
|
executeKillCommand: function (commandArray) {
|
|
try {
|
|
if (commandArray.length < 2) {
|
|
postError(
|
|
"Incorrect usage of kill command. Usage: kill [scriptname] [arg1] [arg2]...",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Kill by PID
|
|
if (typeof commandArray[1] === "number") {
|
|
const pid = commandArray[1];
|
|
const res = killWorkerScript(pid);
|
|
if (res) {
|
|
post(`Killing script with PID ${pid}`);
|
|
} else {
|
|
post(`Failed to kill script with PID ${pid}. No such script exists`);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const s = Player.getCurrentServer();
|
|
const scriptName = Terminal.getFilepath(commandArray[1]);
|
|
const args = [];
|
|
for (let i = 2; i < commandArray.length; ++i) {
|
|
args.push(commandArray[i]);
|
|
}
|
|
const runningScript = s.getRunningScript(scriptName, args);
|
|
if (runningScript == null) {
|
|
postError("No such script is running. Nothing to kill");
|
|
return;
|
|
}
|
|
killWorkerScript(runningScript, s.ip);
|
|
post(`Killing ${scriptName}`);
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
},
|
|
|
|
executeListCommand: function (commandArray) {
|
|
const numArgs = commandArray.length;
|
|
function incorrectUsage() {
|
|
postError(
|
|
"Incorrect usage of ls command. Usage: ls [dir] [| grep pattern]",
|
|
);
|
|
}
|
|
|
|
if (numArgs <= 0 || numArgs > 5 || numArgs === 3) {
|
|
return incorrectUsage();
|
|
}
|
|
|
|
// Grep
|
|
let filter = null; // Grep
|
|
|
|
// Directory path
|
|
let prefix = Terminal.currDir;
|
|
if (!prefix.endsWith("/")) {
|
|
prefix += "/";
|
|
}
|
|
|
|
// If there are 4+ arguments, then the last 3 must be for grep
|
|
if (numArgs >= 4) {
|
|
if (
|
|
commandArray[numArgs - 2] !== "grep" ||
|
|
commandArray[numArgs - 3] !== "|"
|
|
) {
|
|
return incorrectUsage();
|
|
}
|
|
filter = commandArray[numArgs - 1];
|
|
}
|
|
|
|
// If the second argument is not a pipe, then it must be for listing a directory
|
|
if (numArgs >= 2 && commandArray[1] !== "|") {
|
|
prefix = evaluateDirectoryPath(commandArray[1], Terminal.currDir);
|
|
if (prefix != null) {
|
|
if (!prefix.endsWith("/")) {
|
|
prefix += "/";
|
|
}
|
|
if (!isValidDirectoryPath(prefix)) {
|
|
return incorrectUsage();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Root directory, which is the same as no 'prefix' at all
|
|
if (prefix === "/") {
|
|
prefix = null;
|
|
}
|
|
|
|
// Display all programs and scripts
|
|
const allPrograms = [];
|
|
const allScripts = [];
|
|
const allTextFiles = [];
|
|
const allContracts = [];
|
|
const allMessages = [];
|
|
const folders = [];
|
|
|
|
function handleFn(fn, dest) {
|
|
let parsedFn = fn;
|
|
if (prefix) {
|
|
if (!fn.startsWith(prefix)) {
|
|
return;
|
|
} else {
|
|
parsedFn = fn.slice(prefix.length, fn.length);
|
|
}
|
|
}
|
|
|
|
if (filter && !parsedFn.includes(filter)) {
|
|
return;
|
|
}
|
|
|
|
// If the fn includes a forward slash, it must be in a subdirectory.
|
|
// Therefore, we only list the "first" directory in its path
|
|
if (parsedFn.includes("/")) {
|
|
const firstParentDir = getFirstParentDirectory(parsedFn);
|
|
if (filter && !firstParentDir.includes(filter)) {
|
|
return;
|
|
}
|
|
|
|
if (!folders.includes(firstParentDir)) {
|
|
folders.push(firstParentDir);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
dest.push(parsedFn);
|
|
}
|
|
|
|
// Get all of the programs and scripts on the machine into one temporary array
|
|
const s = Player.getCurrentServer();
|
|
for (const program of s.programs) handleFn(program, allPrograms);
|
|
for (const script of s.scripts) handleFn(script.filename, allScripts);
|
|
for (const txt of s.textFiles) handleFn(txt.fn, allTextFiles);
|
|
for (const contract of s.contracts) handleFn(contract.fn, allContracts);
|
|
for (const msgOrLit of s.messages)
|
|
msgOrLit instanceof Message
|
|
? handleFn(msgOrLit.filename, allMessages)
|
|
: handleFn(msgOrLit, allMessages);
|
|
|
|
// Sort the files/folders alphabetically then print each
|
|
allPrograms.sort();
|
|
allScripts.sort();
|
|
allTextFiles.sort();
|
|
allContracts.sort();
|
|
allMessages.sort();
|
|
folders.sort();
|
|
|
|
function postSegments(segments, config) {
|
|
const maxLength = Math.max(...segments.map((s) => s.length)) + 1;
|
|
const filesPerRow = Math.floor(80 / maxLength);
|
|
for (let i = 0; i < segments.length; i++) {
|
|
let row = "";
|
|
for (let col = 0; col < filesPerRow; col++) {
|
|
if (!(i < segments.length)) break;
|
|
row += segments[i];
|
|
row += " ".repeat(maxLength * (col + 1) - row.length);
|
|
i++;
|
|
}
|
|
i--;
|
|
postContent(row, config);
|
|
}
|
|
}
|
|
|
|
const config = { color: "#0000FF" };
|
|
const groups = [
|
|
{ segments: folders, config: config },
|
|
{ segments: allMessages },
|
|
{ segments: allTextFiles },
|
|
{ segments: allPrograms },
|
|
{ segments: allContracts },
|
|
{ segments: allScripts },
|
|
].filter((g) => g.segments.length > 0);
|
|
for (let i = 0; i < groups.length; i++) {
|
|
if (i !== 0) postElement(<br />);
|
|
postSegments(groups[i].segments, groups[i].config);
|
|
}
|
|
},
|
|
|
|
executeMemCommand: function (commandArray) {
|
|
try {
|
|
if (commandArray.length !== 2 && commandArray.length !== 4) {
|
|
postError(
|
|
"Incorrect usage of mem command. usage: mem [scriptname] [-t] [number threads]",
|
|
);
|
|
return;
|
|
}
|
|
|
|
const scriptName = commandArray[1];
|
|
let numThreads = 1;
|
|
if (commandArray.length === 4 && commandArray[2] === "-t") {
|
|
numThreads = Math.round(parseInt(commandArray[3]));
|
|
if (isNaN(numThreads) || numThreads < 1) {
|
|
postError(
|
|
"Invalid number of threads specified. Number of threads must be greater than 1",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const script = Terminal.getScript(scriptName);
|
|
if (script == null) {
|
|
postError("No such script exists!");
|
|
return;
|
|
}
|
|
|
|
const ramUsage = script.ramUsage * numThreads;
|
|
|
|
post(
|
|
`This script requires ${numeralWrapper.formatRAM(
|
|
ramUsage,
|
|
)} of RAM to run for ${numThreads} thread(s)`,
|
|
);
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
},
|
|
|
|
executeNanoCommand: function (commandArray) {
|
|
if (commandArray.length !== 2) {
|
|
postError("Incorrect usage of nano command. Usage: nano [scriptname]");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const filename = commandArray[1];
|
|
if (filename === ".fconf") {
|
|
let text = createFconf();
|
|
Engine.loadScriptEditorContent(filename, text);
|
|
return;
|
|
} else if (isScriptFilename(filename)) {
|
|
const filepath = Terminal.getFilepath(filename);
|
|
const script = Terminal.getScript(filename);
|
|
if (script == null) {
|
|
let code = "";
|
|
if (filename.endsWith(".ns") || filename.endsWith(".js")) {
|
|
code = `export async function main(ns) {
|
|
|
|
}`;
|
|
}
|
|
Engine.loadScriptEditorContent(filepath, code);
|
|
} else {
|
|
Engine.loadScriptEditorContent(filepath, script.code);
|
|
}
|
|
} else if (filename.endsWith(".txt")) {
|
|
const filepath = Terminal.getFilepath(filename);
|
|
const txt = Terminal.getTextFile(filename);
|
|
if (txt == null) {
|
|
Engine.loadScriptEditorContent(filepath);
|
|
} else {
|
|
Engine.loadScriptEditorContent(filepath, txt.text);
|
|
}
|
|
} else {
|
|
postError(
|
|
"Invalid file. Only scripts (.script, .ns, .js), text files (.txt), or .fconf can be edited with nano",
|
|
);
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
},
|
|
|
|
executeScanCommand: function (commandArray) {
|
|
if (commandArray.length !== 1) {
|
|
postError("Incorrect usage of netstat/scan command. Usage: netstat/scan");
|
|
return;
|
|
}
|
|
|
|
// Displays available network connections using TCP
|
|
const currServ = Player.getCurrentServer();
|
|
const servers = currServ.serversOnNetwork.map((_, i) => {
|
|
const server = getServerOnNetwork(currServ, i);
|
|
return {
|
|
hostname: server.hostname,
|
|
ip: server.ip,
|
|
hasRoot: server.hasAdminRights ? "Y" : "N",
|
|
};
|
|
});
|
|
servers.unshift({
|
|
hostname: "Hostname",
|
|
ip: "IP",
|
|
hasRoot: "Root Access",
|
|
});
|
|
const maxHostname = Math.max(...servers.map((s) => s.hostname.length));
|
|
const maxIP = Math.max(...servers.map((s) => s.ip.length));
|
|
for (const server of servers) {
|
|
let entry = server.hostname;
|
|
entry += " ".repeat(maxHostname - server.hostname.length + 1);
|
|
entry += server.ip;
|
|
entry += " ".repeat(maxIP - server.ip.length + 1);
|
|
entry += server.hasRoot;
|
|
post(entry);
|
|
}
|
|
},
|
|
|
|
executeScanAnalyzeCommand: function (depth = 1, all = false) {
|
|
// TODO Using array as stack for now, can make more efficient
|
|
post("~~~~~~~~~~ Beginning scan-analyze ~~~~~~~~~~");
|
|
post(" ");
|
|
|
|
// Map of all servers to keep track of which have been visited
|
|
var visited = {};
|
|
for (const ip in AllServers) {
|
|
visited[ip] = 0;
|
|
}
|
|
|
|
const stack = [];
|
|
const depthQueue = [0];
|
|
const currServ = Player.getCurrentServer();
|
|
stack.push(currServ);
|
|
while (stack.length != 0) {
|
|
const s = stack.pop();
|
|
const d = depthQueue.pop();
|
|
const isHacknet = s instanceof HacknetServer;
|
|
if (!all && s.purchasedByPlayer && s.hostname != "home") {
|
|
continue; // Purchased server
|
|
} else if (visited[s.ip] || d > depth) {
|
|
continue; // Already visited or out-of-depth
|
|
} else if (!all && isHacknet) {
|
|
continue; // Hacknet Server
|
|
} else {
|
|
visited[s.ip] = 1;
|
|
}
|
|
for (var i = s.serversOnNetwork.length - 1; i >= 0; --i) {
|
|
stack.push(getServerOnNetwork(s, i));
|
|
depthQueue.push(d + 1);
|
|
}
|
|
if (d == 0) {
|
|
continue;
|
|
} // Don't print current server
|
|
var titleDashes = Array((d - 1) * 4 + 1).join("-");
|
|
if (Player.hasProgram(Programs.AutoLink.name)) {
|
|
post(
|
|
"<strong>" +
|
|
titleDashes +
|
|
"> <a class='scan-analyze-link'>" +
|
|
s.hostname +
|
|
"</a></strong>",
|
|
false,
|
|
);
|
|
} else {
|
|
post("<strong>" + titleDashes + ">" + s.hostname + "</strong>");
|
|
}
|
|
|
|
var dashes = titleDashes + "--";
|
|
var c = "NO";
|
|
if (s.hasAdminRights) {
|
|
c = "YES";
|
|
}
|
|
post(
|
|
`${dashes}Root Access: ${c}${
|
|
!isHacknet
|
|
? ", Required hacking skill: " + s.requiredHackingSkill
|
|
: ""
|
|
}`,
|
|
);
|
|
if (!isHacknet) {
|
|
post(
|
|
dashes +
|
|
"Number of open ports required to NUKE: " +
|
|
s.numOpenPortsRequired,
|
|
);
|
|
}
|
|
post(dashes + "RAM: " + numeralWrapper.formatRAM(s.maxRam));
|
|
post(" ");
|
|
}
|
|
|
|
var links = document.getElementsByClassName("scan-analyze-link");
|
|
for (let i = 0; i < links.length; ++i) {
|
|
(function () {
|
|
var hostname = links[i].innerHTML.toString();
|
|
links[i].onclick = function () {
|
|
if (
|
|
Terminal.analyzeFlag ||
|
|
Terminal.hackFlag ||
|
|
Terminal.backdoorFlag
|
|
) {
|
|
return;
|
|
}
|
|
Terminal.connectToServer(hostname);
|
|
};
|
|
})(); // Immediate invocation
|
|
}
|
|
},
|
|
|
|
executeScpCommand(commandArray) {
|
|
try {
|
|
if (commandArray.length !== 3) {
|
|
postError(
|
|
"Incorrect usage of scp command. Usage: scp [file] [destination hostname/ip]",
|
|
);
|
|
return;
|
|
}
|
|
const scriptname = Terminal.getFilepath(commandArray[1]);
|
|
if (
|
|
!scriptname.endsWith(".lit") &&
|
|
!isScriptFilename(scriptname) &&
|
|
!scriptname.endsWith(".txt")
|
|
) {
|
|
postError(
|
|
"scp only works for scripts, text files (.txt), and literature files (.lit)",
|
|
);
|
|
return;
|
|
}
|
|
|
|
const destServer = getServer(commandArray[2]);
|
|
if (destServer == null) {
|
|
postError(`Invalid destination. ${commandArray[2]} not found`);
|
|
return;
|
|
}
|
|
const currServ = Player.getCurrentServer();
|
|
|
|
// Scp for lit files
|
|
if (scriptname.endsWith(".lit")) {
|
|
var found = false;
|
|
for (var i = 0; i < currServ.messages.length; ++i) {
|
|
if (
|
|
!(currServ.messages[i] instanceof Message) &&
|
|
currServ.messages[i] == scriptname
|
|
) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
return postError("No such file exists!");
|
|
}
|
|
|
|
for (var i = 0; i < destServer.messages.length; ++i) {
|
|
if (destServer.messages[i] === scriptname) {
|
|
post(scriptname + " copied over to " + destServer.hostname);
|
|
return; // Already exists
|
|
}
|
|
}
|
|
destServer.messages.push(scriptname);
|
|
return post(scriptname + " copied over to " + destServer.hostname);
|
|
}
|
|
|
|
// Scp for txt files
|
|
if (scriptname.endsWith(".txt")) {
|
|
var found = false,
|
|
txtFile;
|
|
for (var i = 0; i < currServ.textFiles.length; ++i) {
|
|
if (currServ.textFiles[i].fn === scriptname) {
|
|
found = true;
|
|
txtFile = currServ.textFiles[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
return postError("No such file exists!");
|
|
}
|
|
|
|
let tRes = destServer.writeToTextFile(txtFile.fn, txtFile.text);
|
|
if (!tRes.success) {
|
|
postError("scp failed");
|
|
return;
|
|
}
|
|
if (tRes.overwritten) {
|
|
post(
|
|
`WARNING: ${scriptname} already exists on ${destServer.hostname} and will be overwriten`,
|
|
);
|
|
post(`${scriptname} overwritten on ${destServer.hostname}`);
|
|
return;
|
|
}
|
|
post(`${scriptname} copied over to ${destServer.hostname}`);
|
|
return;
|
|
}
|
|
|
|
// Get the current script
|
|
let sourceScript = null;
|
|
for (let i = 0; i < currServ.scripts.length; ++i) {
|
|
if (scriptname == currServ.scripts[i].filename) {
|
|
sourceScript = currServ.scripts[i];
|
|
break;
|
|
}
|
|
}
|
|
if (sourceScript == null) {
|
|
postError("scp() failed. No such script exists");
|
|
return;
|
|
}
|
|
|
|
let sRes = destServer.writeToScriptFile(scriptname, sourceScript.code);
|
|
if (!sRes.success) {
|
|
postError(`scp failed`);
|
|
return;
|
|
}
|
|
if (sRes.overwritten) {
|
|
post(
|
|
`WARNING: ${scriptname} already exists on ${destServer.hostname} and will be overwritten`,
|
|
);
|
|
post(`${scriptname} overwritten on ${destServer.hostname}`);
|
|
return;
|
|
}
|
|
post(`${scriptname} copied over to ${destServer.hostname}`);
|
|
} catch (e) {
|
|
Terminal.postThrownError(e);
|
|
}
|
|
},
|
|
|
|
// First called when the "run [program]" command is called. Checks to see if you
|
|
// have the executable and, if you do, calls the executeProgram() function
|
|
runProgram: function (commandArray) {
|
|
if (commandArray.length < 2) {
|
|
return;
|
|
}
|
|
|
|
// Check if you have the program on your computer. If you do, execute it, otherwise
|
|
// display an error message
|
|
const programName = commandArray[1];
|
|
|
|
if (Player.hasProgram(programName)) {
|
|
Terminal.executeProgram(commandArray);
|
|
return;
|
|
}
|
|
post(
|
|
"ERROR: No such executable on home computer (Only programs that exist on your home computer can be run)",
|
|
);
|
|
},
|
|
|
|
// Contains the implementations of all possible programs
|
|
executeProgram: function (commandArray) {
|
|
if (commandArray.length < 2) {
|
|
return;
|
|
}
|
|
|
|
var s = Player.getCurrentServer();
|
|
const programName = commandArray[1];
|
|
const splitArgs = commandArray.slice(1);
|
|
|
|
// TODO: refactor this/these out of Terminal. This logic could reside closer to the Programs themselves.
|
|
/**
|
|
* @typedef {function (server=, args=)} ProgramHandler
|
|
* @param {Server} server The current server the program is being executed against
|
|
* @param {string[]} args The command line arguments passed in to the program
|
|
* @returns {void}
|
|
*/
|
|
/**
|
|
* @type {Object.<string, ProgramHandler}
|
|
*/
|
|
const programHandlers = {};
|
|
programHandlers[Programs.NukeProgram.name] = (server) => {
|
|
if (server.hasAdminRights) {
|
|
post(
|
|
"You already have root access to this computer. There is no reason to run NUKE.exe",
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (
|
|
server.openPortCount >= Player.getCurrentServer().numOpenPortsRequired
|
|
) {
|
|
server.hasAdminRights = true;
|
|
post(
|
|
"NUKE successful! Gained root access to " +
|
|
Player.getCurrentServer().hostname,
|
|
);
|
|
// TODO: Make this take time rather than be instant
|
|
return;
|
|
}
|
|
|
|
post("NUKE unsuccessful. Not enough ports have been opened");
|
|
};
|
|
programHandlers[Programs.BruteSSHProgram.name] = (server) => {
|
|
if (server.sshPortOpen) {
|
|
post("SSH Port (22) is already open!");
|
|
return;
|
|
}
|
|
|
|
server.sshPortOpen = true;
|
|
post("Opened SSH Port(22)!");
|
|
server.openPortCount++;
|
|
};
|
|
programHandlers[Programs.FTPCrackProgram.name] = (server) => {
|
|
if (server.ftpPortOpen) {
|
|
post("FTP Port (21) is already open!");
|
|
return;
|
|
}
|
|
|
|
server.ftpPortOpen = true;
|
|
post("Opened FTP Port (21)!");
|
|
server.openPortCount++;
|
|
};
|
|
programHandlers[Programs.RelaySMTPProgram.name] = (server) => {
|
|
if (server.smtpPortOpen) {
|
|
post("SMTP Port (25) is already open!");
|
|
return;
|
|
}
|
|
|
|
server.smtpPortOpen = true;
|
|
post("Opened SMTP Port (25)!");
|
|
server.openPortCount++;
|
|
};
|
|
programHandlers[Programs.HTTPWormProgram.name] = (server) => {
|
|
if (server.httpPortOpen) {
|
|
post("HTTP Port (80) is already open!");
|
|
return;
|
|
}
|
|
|
|
server.httpPortOpen = true;
|
|
post("Opened HTTP Port (80)!");
|
|
server.openPortCount++;
|
|
};
|
|
programHandlers[Programs.SQLInjectProgram.name] = (server) => {
|
|
if (server.sqlPortOpen) {
|
|
post("SQL Port (1433) is already open!");
|
|
return;
|
|
}
|
|
|
|
server.sqlPortOpen = true;
|
|
post("Opened SQL Port (1433)!");
|
|
server.openPortCount++;
|
|
};
|
|
programHandlers[Programs.ServerProfiler.name] = (server, args) => {
|
|
if (args.length !== 2) {
|
|
post(
|
|
"Must pass a server hostname or IP as an argument for ServerProfiler.exe",
|
|
);
|
|
return;
|
|
}
|
|
|
|
const targetServer = getServer(args[1]);
|
|
if (targetServer == null) {
|
|
post("Invalid server IP/hostname");
|
|
return;
|
|
}
|
|
|
|
if (targetServer instanceof HacknetServer) {
|
|
post(
|
|
`${Programs.ServerProfiler.name} cannot be run on a Hacknet Server.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
post(targetServer.hostname + ":");
|
|
post("Server base security level: " + targetServer.baseDifficulty);
|
|
post("Server current security level: " + targetServer.hackDifficulty);
|
|
post("Server growth rate: " + targetServer.serverGrowth);
|
|
post(
|
|
`Netscript hack() execution time: ${convertTimeMsToTimeElapsedString(
|
|
calculateHackingTime(targetServer, Player) * 1000,
|
|
true,
|
|
)}`,
|
|
);
|
|
post(
|
|
`Netscript grow() execution time: ${convertTimeMsToTimeElapsedString(
|
|
calculateGrowTime(targetServer, Player) * 1000,
|
|
true,
|
|
)}`,
|
|
);
|
|
post(
|
|
`Netscript weaken() execution time: ${convertTimeMsToTimeElapsedString(
|
|
calculateWeakenTime(targetServer, Player) * 1000,
|
|
true,
|
|
)}`,
|
|
);
|
|
};
|
|
programHandlers[Programs.AutoLink.name] = () => {
|
|
post("This executable cannot be run.");
|
|
post(
|
|
"AutoLink.exe lets you automatically connect to other servers when using 'scan-analyze'.",
|
|
);
|
|
post(
|
|
"When using scan-analyze, click on a server's hostname to connect to it.",
|
|
);
|
|
};
|
|
programHandlers[Programs.DeepscanV1.name] = () => {
|
|
post("This executable cannot be run.");
|
|
post("DeepscanV1.exe lets you run 'scan-analyze' with a depth up to 5.");
|
|
};
|
|
programHandlers[Programs.DeepscanV2.name] = () => {
|
|
post("This executable cannot be run.");
|
|
post("DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10.");
|
|
};
|
|
programHandlers[Programs.Flight.name] = () => {
|
|
const numAugReq = Math.round(
|
|
BitNodeMultipliers.DaedalusAugsRequirement * 30,
|
|
);
|
|
const fulfilled =
|
|
Player.augmentations.length >= numAugReq &&
|
|
Player.money.gt(1e11) &&
|
|
Player.hacking_skill >= 2500;
|
|
if (!fulfilled) {
|
|
post(`Augmentations: ${Player.augmentations.length} / ${numAugReq}`);
|
|
postElement(
|
|
<>
|
|
Money: <Money money={Player.money.toNumber()} /> /{" "}
|
|
<Money money={1e11} />
|
|
</>,
|
|
);
|
|
post(`Hacking skill: ${Player.hacking_skill} / 2500`);
|
|
return;
|
|
}
|
|
|
|
post("We will contact you.");
|
|
post("-- Daedalus --");
|
|
};
|
|
programHandlers[Programs.BitFlume.name] = () => {
|
|
const yesBtn = yesNoBoxGetYesButton();
|
|
const noBtn = yesNoBoxGetNoButton();
|
|
yesBtn.innerHTML = "Travel to BitNode Nexus";
|
|
noBtn.innerHTML = "Cancel";
|
|
yesBtn.addEventListener("click", function () {
|
|
hackWorldDaemon(Player.bitNodeN, true);
|
|
return yesNoBoxClose();
|
|
});
|
|
noBtn.addEventListener("click", function () {
|
|
return yesNoBoxClose();
|
|
});
|
|
yesNoBoxCreate(
|
|
"WARNING: USING THIS PROGRAM WILL CAUSE YOU TO LOSE ALL OF YOUR PROGRESS ON THE CURRENT BITNODE.<br><br>" +
|
|
"Do you want to travel to the BitNode Nexus? This allows you to reset the current BitNode " +
|
|
"and select a new one.",
|
|
);
|
|
};
|
|
|
|
if (!programHandlers.hasOwnProperty(programName)) {
|
|
post("Invalid executable. Cannot be run");
|
|
return;
|
|
}
|
|
|
|
programHandlers[programName](s, splitArgs);
|
|
},
|
|
|
|
/**
|
|
* Given a filename, returns that file's full path. This takes into account
|
|
* the Terminal's current directory.
|
|
*/
|
|
getFilepath: function (filename) {
|
|
const path = evaluateFilePath(filename, Terminal.currDir);
|
|
if (path == null) {
|
|
throw new Error(`Invalid file path specified: ${filename}`);
|
|
}
|
|
|
|
if (isInRootDirectory(path)) {
|
|
return removeLeadingSlash(path);
|
|
}
|
|
|
|
return path;
|
|
},
|
|
|
|
/**
|
|
* Given a filename, searches and returns that file. File-type agnostic
|
|
*/
|
|
getFile: function (filename) {
|
|
if (isScriptFilename(filename)) {
|
|
return Terminal.getScript(filename);
|
|
}
|
|
|
|
if (filename.endsWith(".lit")) {
|
|
return Terminal.getLitFile(filename);
|
|
}
|
|
|
|
if (filename.endsWith(".txt")) {
|
|
return Terminal.getTextFile(filename);
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Processes a file path referring to a literature file, taking into account the terminal's
|
|
* current directory + server. Returns the lit file if it exists, and null otherwise
|
|
*/
|
|
getLitFile: function (filename) {
|
|
const s = Player.getCurrentServer();
|
|
const filepath = Terminal.getFilepath(filename);
|
|
for (const lit of s.messages) {
|
|
if (typeof lit === "string" && filepath === lit) {
|
|
return lit;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Processes a file path referring to a script, taking into account the terminal's
|
|
* current directory + server. Returns the script if it exists, and null otherwise.
|
|
*/
|
|
getScript: function (filename) {
|
|
const s = Player.getCurrentServer();
|
|
const filepath = Terminal.getFilepath(filename);
|
|
for (const script of s.scripts) {
|
|
if (filepath === script.filename) {
|
|
return script;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Processes a file path referring to a text file, taking into account the terminal's
|
|
* current directory + server. Returns the text file if it exists, and null otherwise.
|
|
*/
|
|
getTextFile: function (filename) {
|
|
const s = Player.getCurrentServer();
|
|
const filepath = Terminal.getFilepath(filename);
|
|
for (const txt of s.textFiles) {
|
|
if (filepath === txt.fn) {
|
|
return txt;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
postThrownError: function (e) {
|
|
if (e instanceof Error) {
|
|
const errorLabel = "Error: ";
|
|
const errorString = e.toString();
|
|
if (errorString.startsWith(errorLabel)) {
|
|
postError(errorString.slice(errorLabel.length));
|
|
} else {
|
|
postError(errorString);
|
|
}
|
|
}
|
|
},
|
|
|
|
runScript: function (commandArray) {
|
|
if (commandArray.length < 2) {
|
|
dialogBoxCreate(
|
|
`Bug encountered with Terminal.runScript(). Command array has a length of less than 2: ${commandArray}`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const server = Player.getCurrentServer();
|
|
const scriptName = Terminal.getFilepath(commandArray[1]);
|
|
|
|
const runArgs = { "--tail": Boolean, "-t": Number };
|
|
const flags = libarg(runArgs, {
|
|
permissive: true,
|
|
argv: commandArray.slice(2),
|
|
});
|
|
const threadFlag = Math.round(parseFloat(flags["-t"]));
|
|
const tailFlag = flags["--tail"] === true;
|
|
if (flags["-t"] !== undefined && (threadFlag < 0 || isNaN(threadFlag))) {
|
|
postError(
|
|
"Invalid number of threads specified. Number of threads must be greater than 0",
|
|
);
|
|
return;
|
|
}
|
|
const numThreads = !isNaN(threadFlag) && threadFlag > 0 ? threadFlag : 1;
|
|
const args = flags["_"];
|
|
|
|
// Check if this script is already running
|
|
if (findRunningScript(scriptName, args, server) != null) {
|
|
post(
|
|
"ERROR: This script is already running. Cannot run multiple instances",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Check if the script exists and if it does run it
|
|
for (let i = 0; i < server.scripts.length; i++) {
|
|
if (server.scripts[i].filename !== scriptName) {
|
|
continue;
|
|
}
|
|
// Check for admin rights and that there is enough RAM availble to run
|
|
const script = server.scripts[i];
|
|
const ramUsage = script.ramUsage * numThreads;
|
|
const ramAvailable = server.maxRam - server.ramUsed;
|
|
|
|
if (!server.hasAdminRights) {
|
|
post("Need root access to run script");
|
|
return;
|
|
}
|
|
|
|
if (ramUsage > ramAvailable) {
|
|
post(
|
|
"This machine does not have enough RAM to run this script with " +
|
|
numThreads +
|
|
" threads. Script requires " +
|
|
ramUsage +
|
|
"GB of RAM",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Able to run script
|
|
const runningScript = new RunningScript(script, args);
|
|
runningScript.threads = numThreads;
|
|
|
|
const success = startWorkerScript(runningScript, server);
|
|
if (!success) {
|
|
postError(`Failed to start script`);
|
|
return;
|
|
}
|
|
|
|
post(
|
|
`Running script with ${numThreads} thread(s), pid ${
|
|
runningScript.pid
|
|
} and args: ${arrayToString(args)}.`,
|
|
);
|
|
if (tailFlag) {
|
|
logBoxCreate(runningScript);
|
|
}
|
|
return;
|
|
}
|
|
|
|
post("ERROR: No such script");
|
|
},
|
|
|
|
runContract: async function (contractName) {
|
|
// There's already an opened contract
|
|
if (Terminal.contractOpen) {
|
|
return post("ERROR: There's already a Coding Contract in Progress");
|
|
}
|
|
|
|
const serv = Player.getCurrentServer();
|
|
const contract = serv.getContract(contractName);
|
|
if (contract == null) {
|
|
return post("ERROR: No such contract");
|
|
}
|
|
|
|
Terminal.contractOpen = true;
|
|
const res = await contract.prompt();
|
|
|
|
switch (res) {
|
|
case CodingContractResult.Success:
|
|
var reward = Player.gainCodingContractReward(
|
|
contract.reward,
|
|
contract.getDifficulty(),
|
|
);
|
|
post(`Contract SUCCESS - ${reward}`);
|
|
serv.removeContract(contract);
|
|
break;
|
|
case CodingContractResult.Failure:
|
|
++contract.tries;
|
|
if (contract.tries >= contract.getMaxNumTries()) {
|
|
post(
|
|
"Contract <p style='color:red;display:inline'>FAILED</p> - Contract is now self-destructing",
|
|
);
|
|
serv.removeContract(contract);
|
|
} else {
|
|
post(
|
|
`Contract <p style='color:red;display:inline'>FAILED</p> - ${
|
|
contract.getMaxNumTries() - contract.tries
|
|
} tries remaining`,
|
|
);
|
|
}
|
|
break;
|
|
case CodingContractResult.Cancelled:
|
|
default:
|
|
post("Contract cancelled");
|
|
break;
|
|
}
|
|
Terminal.contractOpen = false;
|
|
},
|
|
};
|
|
|
|
export { postNetburnerText, Terminal };
|