added scan-analyze.ts

This commit is contained in:
Vomitblood 2026-05-04 11:20:07 +08:00
parent 21e9d13e61
commit f75ef84f95
12 changed files with 239 additions and 38 deletions

107
src/colors.ts Normal file
View file

@ -0,0 +1,107 @@
import { NS } from "@ns";
/** @param {NS} ns */
export async function main(ns: NS) {
ns.disableLog(`ALL`); // Failures are still logged, this just prevents unnecessary log spam.
ns.tprintf(
`\x1b[1;35mUsing colors in script output with \x1b[1;36mtprint\x1b[1;35m & \x1b[36;1mtprintf\x1b[1;35m (terminal) and \x1b[36;1mprint\x1b[1;35m & \x1b[1;36mprintf\x1b[1;35m (log)`,
);
ns.tprintf(`\n`);
ns.tprintf(`\x1b[1;36m• Using a 4-letter all-CAPS keyword at the start of the string you're printing`);
ns.tprintf(`This gives 4 foreground colors, which can be changed in the game's theme settings.`);
ns.tprintf(` ─ default color, you could use "OKAY" for alignment with other keywords.`);
ns.tprintf(`INFO ─ only the first 4 characters matter, e.g. "INFORMATION" also works.`);
ns.tprintf(`WARN ─ same story, e.g. "WARNING" can also be used.`);
ns.tprintf(`FAIL ─ "ERROR" also works, making it the only 5-letter keyword.`);
ns.tprintf(`\n`);
ns.tprintf(`\x1b[1;36m• Using an ANSI escape sequence to specify attributes`);
ns.tprintf(
`Syntax: \x1b[36m\\x1b[\x1b[35mn\x1b[36mm\x1b[m, replace \x1b[35mn\x1b[m by display attribute(s). Multiple attributes can be set in the same sequence, separated by semicolons.`,
);
ns.tprintf(` 0 ─ \x1b[mall attributes off ─ equivalent to using an empty escape sequence: \x1b[36m\\x1b[m\n`);
ns.tprintf(` 1 ─ \x1b[1mbold text ─ bold characters are wider, so they don't line up with normal text.\n`);
ns.tprintf(` 4 ─ \x1b[4munderline ─ \x1b[4;31msame \x1b[4;33mcolor \x1b[4;35mas \x1b[4;36mthe \x1b[4;37mtext.\n`);
ns.tprintf(`Example: \x1b[36m\\x1b[\x1b[35m1;4\x1b[36mm\x1b[m \x1b[mgives \x1b[1;4mbold underlined text`);
ns.tprintf(`\n`);
ns.tprintf(`\x1b[1;36m• Attributes for 8 colors`);
let palette8colors = ``;
palette8colors += `30-37 ─ 8 foreground colors:`;
for (let i = 30; i <= 37; i++) {
palette8colors += `\x1b[${i}m ${i} \x1b[m`;
}
palette8colors += `\n`;
palette8colors += `40-47 ─ 8 background colors:`;
for (let i = 40; i <= 47; i++) {
if (i < 47) {
palette8colors += `\x1b[${i};37m ${i} \x1b[m`;
} else {
palette8colors += `\x1b[${i};30m ${i} \x1b[m`;
}
}
palette8colors += `\n`;
ns.tprintf(palette8colors);
ns.tprintf(
`Example: \x1b[36m\\x1b[\x1b[35m4;33;44\x1b[36mm\x1b[m gives \x1b[4;33;44myellow underlined text on a blue background`,
);
ns.tprintf(`\n`);
ns.tprintf(`\x1b[1;36m• Attributes for 256 colors`);
let palette256colors = ``;
palette256colors += `38;5;\x1b[35mn\x1b[m ─ Set foreground color to palette index \x1b[35mn\x1b[m\n`;
palette256colors += `48;5;\x1b[35mn\x1b[m ─ Set background color to palette index \x1b[35mn\x1b[m\n`;
palette256colors += `Example: \x1b[36m\\x1b[\x1b[35m38;5;202;48;5;242\x1b[36mm\x1b[m gives \x1b[38;5;202;48;5;242morange text on a gray background\n`;
palette256colors += `\n`;
// 16 basic colors (indices 0 to 15 inclusive)
for (let i = 0; i < 16; i++) {
if (i <= 6 || i === 8 || i === 12) {
// Use light text for better contrast of index against background.
palette256colors += `\x1b[37;48;5;${i}m${String(i).padStart(9)}\x1b[m`;
} else {
// Use dark text for better contrast of index against background.
palette256colors += `\x1b[30;48;5;${i}m${String(i).padStart(9)}\x1b[m`;
}
}
palette256colors += `\n\n`;
// 216 colors (6×6×6 cube) (indices 16 to 231 inclusive)
for (let i = 0; i < 6; i++) {
for (let j = 16; j <= 51; j++) {
const n = i * 36 + j;
if (j < 34) {
palette256colors += `\x1b[37;48;5;${n}m${String(n).padStart(4)}\x1b[m`;
} else {
palette256colors += `\x1b[30;48;5;${n}m${String(n).padStart(4)}\x1b[m`;
}
}
palette256colors += `\n`;
}
palette256colors += `\n`;
// 24 grayscale colors (indices 232 to 255 inclusive)
for (let i = 232; i <= 255; i++) {
if (i < 244) {
palette256colors += `\x1b[37;48;5;${i}m${String(i).padStart(6)}\x1b[m`;
} else {
palette256colors += `\x1b[30;48;5;${i}m${String(i).padStart(6)}\x1b[m`;
}
}
ns.tprintf(palette256colors);
ns.tprintf(`\n`);
ns.tprintf(`\x1b[1;36m• Attributes for 16 million (${Number(2 ** 24).toLocaleString()}) colors`);
let palette16Mcolors = ``;
palette16Mcolors += `38;2;\x1b[35mr;g;b\x1b[m ─ Set foreground color to \x1b[35mred\x1b[m, \x1b[35mgreen\x1b[m, \x1b[35mblue\x1b[m, where each color's value can range from 0 to 255 (0x00 to 0xff)\n`;
palette16Mcolors += `48;2;\x1b[35mr;g;b\x1b[m ─ Set background color to \x1b[35mred\x1b[m, \x1b[35mgreen\x1b[m, \x1b[35mblue\x1b[m, where each color's value can range from 0 to 255 (0x00 to 0xff)\n`;
palette16Mcolors += `Example: \x1b[36m\\x1b[\x1b[35m38;2;157;0;255;48;2;255;222;33\x1b[36mm\x1b[m gives \x1b[38;2;157;0;255;48;2;255;222;33mpurple text on a yellow background\x1b[m\n`;
palette16Mcolors += `\n`;
palette16Mcolors += `Don't worry, this script does not print the full 16 million color palette ;)\n`;
palette16Mcolors += `\n`;
ns.tprintf(palette16Mcolors);
}

View file

@ -1,15 +1,5 @@
import { NS, Server } from "@ns"; import { NS, Server } from "@ns";
// TODO: incomplete
// define a new interface for the new analysis results
interface ServerAnalysis {
hostname: string;
hasRootAccess: boolean;
requiredHackingSkill: number;
portsRequiredForNuke: number;
maxRam: number;
}
export const analyze = (ns: NS, hostnames: string[]) => { export const analyze = (ns: NS, hostnames: string[]) => {
hostnames.forEach((hostname) => { hostnames.forEach((hostname) => {
// skip darknet servers // skip darknet servers
@ -21,16 +11,7 @@ export const analyze = (ns: NS, hostnames: string[]) => {
// get the server object for the hostname // get the server object for the hostname
const server = ns.getServer(hostname) as Server; const server = ns.getServer(hostname) as Server;
// create a new object that matches the ServerAnalysis interface ns.tprint(server);
const analysis: ServerAnalysis = {
hostname: server.hostname,
hasRootAccess: server.hasAdminRights,
requiredHackingSkill: server.requiredHackingSkill ?? 0,
portsRequiredForNuke: server.numOpenPortsRequired ?? 0,
maxRam: server.maxRam,
};
ns.tprint(analysis);
}); });
}; };

View file

@ -81,3 +81,7 @@ export const upgradeAll = (ns: NS) => {
ns.tprint(`Next tier (${utils.format.ram(nextRam)} GB) will cost: ${utils.format.money(nextTierCost)}`); ns.tprint(`Next tier (${utils.format.ram(nextRam)} GB) will cost: ${utils.format.money(nextTierCost)}`);
} }
}; };
export const main = (ns: NS) => {
upgradeAll(ns);
};

View file

View file

@ -1,5 +1,5 @@
import { NS } from "@ns"; import { NS } from "@ns";
import { scan } from "../ezgame/scan"; import { scan } from "./scan";
export const killall = (ns: NS): void => { export const killall = (ns: NS): void => {
const allHosts = scan(ns); const allHosts = scan(ns);

View file

@ -1,5 +1,5 @@
import { NS } from "@ns"; import { NS } from "@ns";
import { scan } from "../ezgame/scan"; import { scan } from "./scan";
// get root access to all the servers that we can // get root access to all the servers that we can
// then return a list of all the servers we have root access to // then return a list of all the servers we have root access to

View file

@ -0,0 +1,97 @@
import { utils } from "@/utils";
import { NS, Server } from "@ns";
export interface ScanAnalyzeOptions {
excludeCloudServers?: boolean;
quiet?: boolean;
}
export interface ServerNode {
hostname: string;
parent: string | null;
children: string[];
depth: number;
}
export const scanAnalyze = (ns: NS, options: ScanAnalyzeOptions = {}): Map<string, ServerNode> => {
const tree = new Map<string, ServerNode>();
// start BFS from home
const queue: { hostname: string; parent: string | null; depth: number }[] = [
{ hostname: "home", parent: null, depth: 0 },
];
while (queue.length > 0) {
const { hostname, parent, depth } = queue.shift()!;
// skip if already visited
if (tree.has(hostname)) continue;
// get neighbors
const neighbors = ns.scan(hostname);
// children are neighbors that are not the parent
const children = neighbors.filter((neighbor) => {
if (neighbor === parent) return false;
if (options.excludeCloudServers && ns.getServer(neighbor).purchasedByPlayer) return false;
return true;
});
// add to tree
tree.set(hostname, { hostname, parent, children, depth });
// enqueue children
for (const child of children) {
if (!tree.has(child)) {
queue.push({ hostname: child, parent: hostname, depth: depth + 1 });
}
}
}
if (!options.quiet) {
// we are sure that home is always in the tree since its the starting point
printTree(ns, tree.get("home") as ServerNode, tree);
}
return tree;
};
const printTree = (
ns: NS,
node: ServerNode,
tree: Map<string, ServerNode>,
prefix: string[] = [" "],
isLast = true,
): void => {
// ns.scan() does not return darknet servers,
// so we can just safely assume that the server type returned is just `Server`.
// always start with home
const server = ns.getServer(node.hostname) as Server;
const titlePrefix = prefix.slice(0, prefix.length - 1).join("") + (isLast ? "┗ " : "┣ ");
const infoPrefix = prefix.join("") + (node.children.length > 0 ? "┃ " : " ");
ns.tprint(`* ${titlePrefix}${node.hostname}`);
ns.tprint(
`* ${infoPrefix}Root Access: ${server.hasAdminRights ? "YES" : "NO"}, Required hacking skill: ${
server.requiredHackingSkill ?? 0
}`,
);
ns.tprint(`* ${infoPrefix}Number of open ports required to NUKE: ${server.numOpenPortsRequired ?? 0}`);
ns.tprint(`* ${infoPrefix}RAM: ${utils.format.ram(server.maxRam)}, Player purchased: ${server.purchasedByPlayer}`);
// recursive
node.children.forEach((childName, i) => {
const childNode = tree.get(childName);
if (!childNode) {
// this should never happen since we build the tree from the scan results, but just in case
throw new Error(`Child node ${childName} not found in tree`);
}
const childIsLast = i === node.children.length - 1;
printTree(ns, childNode, tree, [...prefix, childIsLast ? " " : "┃ "], childIsLast);
});
};
export const main = (ns: NS) => {
scanAnalyze(ns, { quiet: false, excludeCloudServers: true });
};

View file

@ -20,9 +20,6 @@ export const scan = (ns: NS, options: ScanOptions = {}): string[] => {
ns.scan(h).forEach((n) => allHosts.add(n)); ns.scan(h).forEach((n) => allHosts.add(n));
}); });
// remove home from the set
allHosts.delete("home");
// now we start the filtering into another new list // now we start the filtering into another new list
const filteredHosts = Array.from(allHosts).filter((host: string) => { const filteredHosts = Array.from(allHosts).filter((host: string) => {
if (rootAccess !== undefined && ns.hasRootAccess(host) !== rootAccess) return false; if (rootAccess !== undefined && ns.hasRootAccess(host) !== rootAccess) return false;
@ -40,5 +37,7 @@ export const scan = (ns: NS, options: ScanOptions = {}): string[] => {
}; };
export const main = (ns: NS) => { export const main = (ns: NS) => {
ns.tprint(scan(ns)); const results = scan(ns);
ns.tprint(results);
ns.tprint(`Total hosts found: ${results.length}`);
}; };

View file

@ -1,5 +1,5 @@
import { NS } from "@ns"; import { NS } from "@ns";
import { scan } from "../ezgame/scan"; import { scan } from "./scan";
export const scriptCleanup = (ns: NS) => { export const scriptCleanup = (ns: NS) => {
// get all hosts with root access // get all hosts with root access

View file

@ -1,5 +1,5 @@
import { NS } from "@ns"; import { NS } from "@ns";
import { scan } from "../ezgame/scan"; import { scan } from "./scan";
export const scriptPropagator = (ns: NS) => { export const scriptPropagator = (ns: NS) => {
// get all hosts with root access // get all hosts with root access

View file

@ -1,5 +1,5 @@
import { NS } from "@ns"; import { NS } from "@ns";
import { scan } from "../ezgame/scan"; import { scan } from "./scan";
interface StartOptions { interface StartOptions {
threads?: number; // explicit thread count threads?: number; // explicit thread count
@ -53,8 +53,23 @@ export const startall = (ns: NS, scriptName: string, options: StartOptions = {})
ns.tprint(`Hosts affected: ${hostsStartedOn}`); ns.tprint(`Hosts affected: ${hostsStartedOn}`);
}; };
export const main = (ns: NS) => { export const main = async (ns: NS) => {
// get the arguments from the command line if (ns.args.length === 0) {
const args = ns.args as string[]; const target = [
startall(ns, "super.js", { args: args }); (
await ns.prompt("You didn't specify the target in the arguments. Please specify it here.", {
type: "text",
})
).toString(),
];
if (!target) {
ns.alert("You still did not specify anything you dumb fuck. Quitting now.");
return;
}
startall(ns, "super.js", { args: target });
} else {
// get the arguments from the command line
const args = ns.args as string[];
startall(ns, "super.js", { args: args });
}
}; };

View file

@ -1,8 +1,6 @@
import { NS } from "@ns"; import { NS } from "@ns";
import { cloud } from "./ezgame/cloud";
export const main = (ns: NS) => { export const main = async (ns: NS) => {
const upgradeCost = ns.cloud.getServerUpgradeCost("worker-1", 2); const bruh = await ns.prompt("hello", { type: "text", choices: ["asdf1", "asdf2"] });
console.log(upgradeCost); console.log(bruh);
cloud.upgradeAll(ns);
}; };