From f75ef84f957d522ade515155d40cf56752b1ea80 Mon Sep 17 00:00:00 2001 From: Vomitblood Date: Mon, 4 May 2026 11:20:07 +0800 Subject: [PATCH] added scan-analyze.ts --- src/colors.ts | 107 ++++++++++++++++++++++++++++++++ src/ezgame/analyze.ts | 21 +------ src/ezgame/cloud/upgrade-all.ts | 4 ++ src/ezgame/index.ts | 0 src/ezgame/killall.ts | 2 +- src/ezgame/nuke.ts | 2 +- src/ezgame/scan-analyze.ts | 97 +++++++++++++++++++++++++++++ src/ezgame/scan.ts | 7 +-- src/ezgame/script-cleanup.ts | 2 +- src/ezgame/script-propagator.ts | 2 +- src/ezgame/startall.ts | 25 ++++++-- src/test.ts | 8 +-- 12 files changed, 239 insertions(+), 38 deletions(-) create mode 100644 src/colors.ts delete mode 100644 src/ezgame/index.ts create mode 100644 src/ezgame/scan-analyze.ts diff --git a/src/colors.ts b/src/colors.ts new file mode 100644 index 0000000..749146a --- /dev/null +++ b/src/colors.ts @@ -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); +} diff --git a/src/ezgame/analyze.ts b/src/ezgame/analyze.ts index 7bd75fd..1c9f2cb 100644 --- a/src/ezgame/analyze.ts +++ b/src/ezgame/analyze.ts @@ -1,15 +1,5 @@ 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[]) => { hostnames.forEach((hostname) => { // skip darknet servers @@ -21,16 +11,7 @@ export const analyze = (ns: NS, hostnames: string[]) => { // get the server object for the hostname const server = ns.getServer(hostname) as Server; - // create a new object that matches the ServerAnalysis interface - const analysis: ServerAnalysis = { - hostname: server.hostname, - hasRootAccess: server.hasAdminRights, - requiredHackingSkill: server.requiredHackingSkill ?? 0, - portsRequiredForNuke: server.numOpenPortsRequired ?? 0, - maxRam: server.maxRam, - }; - - ns.tprint(analysis); + ns.tprint(server); }); }; diff --git a/src/ezgame/cloud/upgrade-all.ts b/src/ezgame/cloud/upgrade-all.ts index f606f57..c0a5d91 100644 --- a/src/ezgame/cloud/upgrade-all.ts +++ b/src/ezgame/cloud/upgrade-all.ts @@ -81,3 +81,7 @@ export const upgradeAll = (ns: NS) => { ns.tprint(`Next tier (${utils.format.ram(nextRam)} GB) will cost: ${utils.format.money(nextTierCost)}`); } }; + +export const main = (ns: NS) => { + upgradeAll(ns); +}; diff --git a/src/ezgame/index.ts b/src/ezgame/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/ezgame/killall.ts b/src/ezgame/killall.ts index c359f96..21d0bfd 100644 --- a/src/ezgame/killall.ts +++ b/src/ezgame/killall.ts @@ -1,5 +1,5 @@ import { NS } from "@ns"; -import { scan } from "../ezgame/scan"; +import { scan } from "./scan"; export const killall = (ns: NS): void => { const allHosts = scan(ns); diff --git a/src/ezgame/nuke.ts b/src/ezgame/nuke.ts index 15fc03a..8a8df6b 100644 --- a/src/ezgame/nuke.ts +++ b/src/ezgame/nuke.ts @@ -1,5 +1,5 @@ import { NS } from "@ns"; -import { scan } from "../ezgame/scan"; +import { scan } from "./scan"; // get root access to all the servers that we can // then return a list of all the servers we have root access to diff --git a/src/ezgame/scan-analyze.ts b/src/ezgame/scan-analyze.ts new file mode 100644 index 0000000..b512d60 --- /dev/null +++ b/src/ezgame/scan-analyze.ts @@ -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 => { + const tree = new Map(); + + // 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, + 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 }); +}; diff --git a/src/ezgame/scan.ts b/src/ezgame/scan.ts index 918269e..22665f7 100644 --- a/src/ezgame/scan.ts +++ b/src/ezgame/scan.ts @@ -20,9 +20,6 @@ export const scan = (ns: NS, options: ScanOptions = {}): string[] => { 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 const filteredHosts = Array.from(allHosts).filter((host: string) => { 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) => { - ns.tprint(scan(ns)); + const results = scan(ns); + ns.tprint(results); + ns.tprint(`Total hosts found: ${results.length}`); }; diff --git a/src/ezgame/script-cleanup.ts b/src/ezgame/script-cleanup.ts index 4142b97..7c98250 100644 --- a/src/ezgame/script-cleanup.ts +++ b/src/ezgame/script-cleanup.ts @@ -1,5 +1,5 @@ import { NS } from "@ns"; -import { scan } from "../ezgame/scan"; +import { scan } from "./scan"; export const scriptCleanup = (ns: NS) => { // get all hosts with root access diff --git a/src/ezgame/script-propagator.ts b/src/ezgame/script-propagator.ts index 406e877..3ca3b27 100644 --- a/src/ezgame/script-propagator.ts +++ b/src/ezgame/script-propagator.ts @@ -1,5 +1,5 @@ import { NS } from "@ns"; -import { scan } from "../ezgame/scan"; +import { scan } from "./scan"; export const scriptPropagator = (ns: NS) => { // get all hosts with root access diff --git a/src/ezgame/startall.ts b/src/ezgame/startall.ts index 47f3054..037ab02 100644 --- a/src/ezgame/startall.ts +++ b/src/ezgame/startall.ts @@ -1,5 +1,5 @@ import { NS } from "@ns"; -import { scan } from "../ezgame/scan"; +import { scan } from "./scan"; interface StartOptions { threads?: number; // explicit thread count @@ -53,8 +53,23 @@ export const startall = (ns: NS, scriptName: string, options: StartOptions = {}) ns.tprint(`Hosts affected: ${hostsStartedOn}`); }; -export const main = (ns: NS) => { - // get the arguments from the command line - const args = ns.args as string[]; - startall(ns, "super.js", { args: args }); +export const main = async (ns: NS) => { + if (ns.args.length === 0) { + const target = [ + ( + 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 }); + } }; diff --git a/src/test.ts b/src/test.ts index 8358873..9e16d83 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,8 +1,6 @@ import { NS } from "@ns"; -import { cloud } from "./ezgame/cloud"; -export const main = (ns: NS) => { - const upgradeCost = ns.cloud.getServerUpgradeCost("worker-1", 2); - console.log(upgradeCost); - cloud.upgradeAll(ns); +export const main = async (ns: NS) => { + const bruh = await ns.prompt("hello", { type: "text", choices: ["asdf1", "asdf2"] }); + console.log(bruh); };