This commit is contained in:
Zakary Timson 2022-03-15 20:41:23 -04:00
parent 1a0f8e6b16
commit 35fcc721fa
10 changed files with 261 additions and 337 deletions

View File

@ -13,7 +13,6 @@ These scripts are for playing the [open source](https://github.com/danielyxie/bi
- [network-graph.js](#network-graphjs)
- [rootkit.js](#rootkitjs)
- [update.js](#updatejs)
- [vanguard.js](#vanguardjs)
## Quick Start
@ -151,10 +150,12 @@ Usage: run network-graph.js [OPTIONS] [DEVICE]
DEVICE Point to start scan from, defaults to current machine
Options:
-d --depth Depth to scan to, defaults to 3
-f --filter Display devices matching name
-r --regex Display devices matching pattern
-v --verbose Displays the required hack level & ports needed to root: (level|port)
-d --depth Depth to scan to, defaults is 3
-f --filter Filter to device matching name
-e --regex Filter to devices matching pattern
-r --rooted Filter to devices that have been rooted
-n --not-rooted Filter to devices that have not been rooted
-v --verbose Display the required hack level & number of ports to root: (level|port)
-h --help Display this help message
```
@ -204,24 +205,3 @@ Options:
--no-banner Hide the banner (Used internally)
-h --help Display this help message
```
### [vanguard.js](./scripts/vanguard.js)
**RAM:** 1.90 GB
Weaken a device indefinitely.
```
[home ~/scripts]> run /scripts/vanguard.js --help
Running script with 1 thread(s), pid 1 and args: ["--help"].
/scripts/vanguard.js:
Weaken a device indefinitely.
Usage: run vanguard.js [OPTIONS] [DEVICE]
run vanguard.js --help
DEVICE Device to weaken, defaults to the current machine
Options:
-l --limit Limit the number of times to run
-h --help Display this help message
```

View File

@ -1,48 +1,38 @@
import {ArgError, ArgParser} from './scripts/lib/arg-parser';
import {terminal} from './scripts/lib/utils';
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
import {pruneTree, scanNetwork, terminal} from '/scripts/lib/utils';
/**
* Connect to a device on a different network.
* BitBurner autocomplete
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
* @returns {string[]} - Pool of autocomplete options
*/
export function autocomplete(data) {
return [...data.servers];
}
/**
* Search the network for a device and connect to it.
* @param ns {NS} - BitBurner API
*/
export function main(ns) {
// Setup
ns.disableLog('ALL');
let args;
const argParser = new ArgParser('connect.js', 'Connect to a device on a different network.', null, [
const argParser = new ArgParser('connect.js', 'Search the network for a device and connect to it.', null, [
{name: 'device', desc: 'Device to connect to', default: ns.getHostname(), type: 'string'}
]);
try {
args = argParser.parse(ns.args);
// Run
const args = argParser.parse(ns.args);
const [devices, network] = scanNetwork(ns);
pruneTree(network, d => d == args['device']);
let current = network, name;
while(name = Object.keys(current)[0]) {
terminal(`connect ${name}`);
current = current[name];
}
} catch(err) {
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
throw err;
}
/**
* Find path to a device recursively
* @param device {string} - Device to locate
* @param current {string} - Current device to scan
* @param path {string[]} - Path the the current device
* @param all {Set} - Stop devices from being scanned
* @returns {string[]} - Path to the located device
*/
function find(device, current = 'home', path = [current], blacklist = new Set()) {
blacklist.add(current);
const newDevices = ns.scan(current).filter(d => !blacklist.has(d));
if(newDevices.length == 0) return [];
if(newDevices.find(d => d == device)) return [...path, device];
return newDevices.map(d => find(device, d, [...path, d], blacklist)).find(p => p && p.length);
}
// Run
const path = find(args['device']);
path.splice(0, 1); // Delete 'home' from from the path
for(let d of path) {
terminal(`connect ${d}`);
}
}
export function autocomplete(data) {
return [...data.servers];
}

View File

@ -1,4 +1,14 @@
import {ArgError, ArgParser} from './scripts/lib/arg-parser';
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
import {scanNetwork} from '/scripts/lib/utils';
/**
* BitBurner autocomplete
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
* @returns {string[]} - Pool of autocomplete options
*/
export function autocomplete(data) {
return [...data.scripts, '{{TARGET}}'];
}
/**
* Search the network for targets to execute a script against.
@ -13,45 +23,24 @@ export async function main(ns) {
{name: 'cpu', desc: 'Number of CPU threads to use with script', flags: ['-c', '--cpu'], default: 1, type: 'num'},
{name: 'depth', desc: 'Depth to scan to, defaults to 3', flags: ['-d', '--depth'], default: Infinity, type: 'num'},
{name: 'level', desc: 'Exclude targets with higher hack level, defaults to current hack level', flags: ['-l', '--level'], default: ns.getHackingLevel(), type: 'num'},
{name: 'ports', desc: 'Exclute targets with too many closed ports', flags: ['-p', '--ports'], optional: true, default: Infinity, type: 'num'},
{name: 'ports', desc: 'Exclute targets with too many closed ports', flags: ['-p', '--ports'], default: Infinity, type: 'num'},
{name: 'silent', desc: 'Surpress program output', flags: ['-s', '--silent'], type: 'bool'}
], true);
let args;
try {
args = argParser.parse(ns.args);
} catch(err) {
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
throw err;
}
/**
* Recursively search network & build a tree
* @param host {string} - Point to scan from
* @param depth {number} - Current scanning depth
* @param blacklist {String[]} - Devices already discovered
* @returns Dicionary of discovered devices
*/
function scan(target = 'home', depth = 1, found = new Set()) {
if(found.size == 0) found.add(target);
ns.scan(target).filter(t => !found.has(t)).forEach(t => {
found.add(t);
scan(t, depth + 1, found);
});
found.delete('home');
return Array.from(found);
}
// Run
const targets = scan();
const args = argParser.parse(ns.args);
const [devices, network] = scanNetwork(ns);
let complete = 0, failed = 0, skipped = 0;
for(let target of targets) {
if(target == 'home') continue;
if(args['level'] < ns.getServerRequiredHackingLevel(target) || args['ports'] < ns.getServerNumPortsRequired(target)) {
for(let device of devices) {
// Skip invalid devices
if(device == 'home' || args['level'] < ns.getServerRequiredHackingLevel(device) || args['ports'] < ns.getServerNumPortsRequired(device)) {
skipped++;
continue;
}
const scriptArgs = args['args'].map(arg => arg == '{{TARGET}}' ? target : arg);
// Start script
const scriptArgs = args['args'].map(arg => arg.toUpperCase() == '{{TARGET}}' ? device : arg);
const pid = ns.run(args['script'], args['cpu'], ...scriptArgs);
if(pid == 0) {
failed++;
@ -59,14 +48,21 @@ export async function main(ns) {
}
// Wait for script to finish
do { await ns.sleep(1000); }
while(ns.scriptRunning(args['script'], 'home'));
while(ns.scriptRunning(args['script'], 'home'))
await ns.sleep(1000);
complete++;
}
// Output report
if(!args['silent']) {
ns.tprint('===================================================');
ns.tprint(`Crawler Report: ${targets.length} Device${targets.length > 1 ? 's' : ''}`);
ns.tprint(`Crawler Report: ${devices.length} Device${devices.length > 1 ? 's' : ''}`);
ns.tprint('===================================================');
ns.tprint(`Complete: ${complete}\tFailed: ${failed}\tSkipped: ${skipped}`);
ns.tprint('');
}
} catch(err) {
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
throw err;
}
}

View File

@ -7,37 +7,17 @@ import {ArgError, ArgParser} from './scripts/lib/arg-parser';
export async function main(ns) {
// Setup
ns.disableLog('ALL');
const historyLength = 17;
const messageHistory = Array(historyLength).fill('');
let args, nodeCount = ns.hacknet.numNodes();
const argParser = new ArgParser('hacknet-manager.js', 'Buy, upgrade & manage Hacknet nodes automatically. Tail for live updates.', null, [
{name: 'limit', desc: 'Limit the number of nodes the manager will buy, defaults to 8', optional: true, default: 8, type: 'num'},
{name: 'balance', desc: 'Prevent spending bellow point', flags: ['-b', '--balance'], type: 'num'}
{name: 'balance', desc: 'Prevent spending bellow point', flags: ['-b', '--balance'], type: 'num'},
{name: 'sleep', desc: 'Amount of time to wait between purchases, defaults to 1 (second)', flags: ['-s', '--sleep'], default: 1, type: 'num'}
]);
try {
args = argParser.parse(ns.args);
} catch(err) {
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
throw err;
}
/**
* Print header with logs
* @param message - message to append to logs
*/
function log(message) {
ns.clearLog();
ns.print('===================================================');
ns.print(`Hacknet Manager: ${nodeCount}/${args['limit']} Nodes`);
ns.print('===================================================');
if(message != null) messageHistory.push(message);
messageHistory.splice(0, messageHistory.length - historyLength);
messageHistory.forEach(m => ns.print(m));
}
// Run
log();
const args = argParser.parse(ns.args);
let nodeCount = ns.hacknet.numNodes();
const logger = new Logger(ns, () => [() => `Hacknet Manager: ${nodeCount}/${args['limit']}`]);
while(true) {
const balance = ns.getServerMoneyAvailable('home');
@ -45,7 +25,7 @@ export async function main(ns) {
if(nodeCount < args['limit'] && balance - ns.hacknet.getPurchaseNodeCost() >= args['balance']) {
nodeCount++;
ns.hacknet.purchaseNode();
log(`Buying Node ${nodeCount}`);
logger.log(`Buying Node ${nodeCount}`);
} else {
// Create an ordered list of nodes by their cheapest upgrade
const nodes = Array(nodeCount).fill(null)
@ -92,12 +72,16 @@ export async function main(ns) {
// Apply the cheapest upgrade
if(nodes.length && balance - nodes[0].bestUpgrade.cost >= args['balance']) {
const cost = Math.round(nodes[0].bestUpgrade.cost * 100) / 100;
log(`Node ${nodes[0].index} - ${nodes[0].bestUpgrade.name} ${nodes[0][nodes[0].bestUpgrade.name] + 1} - $${cost}`);
logger.log(`Node ${nodes[0].index} - ${nodes[0].bestUpgrade.name} ${nodes[0][nodes[0].bestUpgrade.name] + 1} - $${cost}`);
nodes[0].bestUpgrade.purchase();
}
}
// Check again in 1s
await ns.sleep(1000);
await ns.sleep(args['sleep'] * 1000);
}
} catch(err) {
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
throw err;
}
}

View File

@ -12,6 +12,7 @@ export class Logger {
this.fns = lineFns;
this.historyLen -= fns.length * 2;
this.history = Array(this.historyLen).fill('');
this.log();
}
/**

View File

@ -1,7 +1,31 @@
/**
* Copy a file & scan it for dependencies copying them as well.
* @param ns {NS} - BitBurner API
* @param src {string} - File to scan & copy
* @param device {string} - Device to copy files to
* @returns {string[]} - Array of coppied files
*/
export async function copyWithDependencies(ns, src, device) {
const queue = [src], found = [src];
while(queue.length) {
const file = queue.splice(0, 1)[0];
const imports = new RegExp(/from ["']\.?(\/.+)["']/g);
const script = await ns.read(file);
let match;
while((match = imports.exec(script)) != null) {
const path = `${match[1]}.js`;
queue.push(path);
found.push(path);
}
}
await ns.scp(found, device);
return found;
}
/**
* Print a download bar to the terminal.
* @params ns {NS} - Bitburner API
* @params file - Filename to display with progress bar
* @param ns {NS} - BitBurner API
* @param file - Filename to display with progress bar
*/
export async function downloadPrint(ns, file) {
const speed = ~~(Math.random() * 100) / 10;
@ -9,12 +33,50 @@ export async function downloadPrint(ns, file) {
await slowPrint(ns, `${file}${spacing}[==================>] 100%\t(${speed} MB/s)`);
}
export function flatten(object) {
}
/**
* **Impure:** Prune tree down to keys which pass function
* @param tree {object} - Tree to search
* @param fn {(key: string) => boolean} - Function to test each key with
* @returns {boolean} - True if a match was found
*/
export function pruneTree(tree, fn) {
return !!Object.keys(tree).map(k => {
let matches = fn(k), children = Object.keys(k), childrenMatch = false;
if(children.length) childrenMatch = pruneTree(tree[k], fn);
if(!childrenMatch && !matches) delete tree[k];
return childrenMatch || matches;
}).find(el => el);
}
/**
* Scan the network of a given device.
* @param ns {NS} - BitBurner API
* @param device {string} - Device network that will be scanned
* @param maxDepth - Depth to scan to
* @returns {[string[], Object]} - A tuple including an array of discovered devices & a tree of the network
*/
export function scanNetwork(ns, device = ns.getHostname(), maxDepth = Infinity) {
let discovered = [device];
function scan (device, depth = 1) {
if(depth > maxDepth) return {};
const localTargets = ns.scan(device).filter(newDevice => !discovered.includes(newDevice));
discovered = [...discovered, ...localTargets];
return localTargets.reduce((acc, device) => ({...acc, [device]: scan(device, depth + 1)}), {});
}
const network = scan(device);
return [discovered.slice(1), network];
}
/**
* Print text to the terminal & then delay for a random amount of time to emulate execution time.
* @params ns {NS} - Bitburner API
* @params message {string} - Text to display
* @params min {number} - minimum amount of time to wait after printing text
* @params max {number} - maximum amount of time to wait after printing text
* @param ns {NS} - BitBurner API
* @param message {string} - Text to display
* @param min {number} - minimum amount of time to wait after printing text
* @param max {number} - maximum amount of time to wait after printing text
*/
export async function slowPrint(ns, message, min = 0.5, max = 1.5) {
const time = ~~(Math.random() * (max * 1000 - min * 1000)) + min * 1000;
@ -24,7 +86,7 @@ export async function slowPrint(ns, message, min = 0.5, max = 1.5) {
/**
* Write a command to the terminal.
* @params command {string} - Command that will be run
* @param command {string} - Command that will be run
* @returns {string} - Response
*/
export async function terminal(command) {

View File

@ -10,7 +10,7 @@ export async function main(ns) {
const historyLength = 15;
const messageHistory = Array(historyLength).fill('');
let maxBalance, balance, minSecurity, security;
const argParser = new ArgParser('miner.js', 'Weaken, Grow, Hack loop to "mine" machine for money.', null, [
const argParser = new ArgParser('miner.js', 'Weaken, Grow, Hack loop to "mine" machine for money. Tail for live updates', null, [
{name: 'device', desc: 'Device to mine, defaults to current machine', optional: true, default: ns.getHostname(), type: 'string'}
]);
let args;

View File

@ -1,14 +1,30 @@
import {ArgError, ArgParser} from './scripts/lib/arg-parser';
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
import {pruneTree, scanNetwork} from '/scripts/lib/utils';
/**
* BitBurner autocomplete
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
* @returns {string[]} - Pool of autocomplete options
*/
export function autocomplete(data) {
return [...data.servers];
}
/**
* Scan the network for devices and display as an ASCII tree.
* @param ns {NS} - BitBurner API
*/
export async function main(ns) {
// Setup
ns.disableLog('ALL');
const argParser = new ArgParser('network-graph.js', 'Scan the network for devices and display as an ASCII tree:\n home\n ├─ n00dles (ROOTED)\n | └─ max-hardware (80|1)\n | └─ neo-net (50|1)\n ├─ foodnstuff (ROOTED)\n └─ sigma-cosmetics (ROOTED)', null, [
{name: 'device', desc: 'Point to start scan from, defaults to current machine', optional: true, default: ns.getHostname(), type: 'string'},
{name: 'depth', desc: 'Depth to scan to, defaults to 3', flags: ['-d', '--depth'], default: Infinity, type: 'num'},
{name: 'filter', desc: 'Display devices matching name', flags: ['-f', '--filter'], type: 'string'},
{name: 'regex', desc: 'Display devices matching pattern', flags: ['-r', '--regex'], type: 'string'},
{name: 'verbose', desc: 'Displays the required hack level & ports needed to root: (level|port)', flags: ['-v', '--verbose'], type: 'bool'},
{name: 'depth', desc: 'Depth to scan to, defaults is 3', flags: ['-d', '--depth'], default: Infinity, type: 'num'},
{name: 'filter', desc: 'Filter to device matching name', flags: ['-f', '--filter'], type: 'string'},
{name: 'regex', desc: 'Filter to devices matching pattern', flags: ['-e', '--regex'], type: 'string'},
{name: 'rooted', desc: 'Filter to devices that have been rooted', flags: ['-r', '--rooted'], type: 'bool'},
{name: 'notRooted', desc: 'Filter to devices that have not been rooted', flags: ['-n', '--not-rooted'], type: 'bool'},
{name: 'verbose', desc: 'Display the required hack level & number of ports to root: (level|port)', flags: ['-v', '--verbose'], type: 'bool'},
]);
let args;
try {
@ -18,81 +34,31 @@ export async function main(ns) {
throw err;
}
/**
* Prune tree down to devices that match name or pattern.
* @param tree {object} - Tree to search
* @param find {string} - Device name or pattern to search for
* @param regex {boolean} - True to use regex, false for raw check
* @returns {object} - Pruned tree
*/
function filter(tree, find, regex = false) {
const found = new Set();
function buildWhitelist(tree, find, path = []) {
const keys = Object.keys(tree);
if(!keys.length) return;
Object.keys(tree).forEach(n => {
const matches = regex ? new RegExp(find).test(n) : n == find;
if(n == 'n00dles') console.log(n, find, matches);
if(matches) {
found.add(n);
path.forEach(p => found.add(p));
}
buildWhitelist(tree[n], find, [...path, n]);
})
}
function prune(tree, whitelist) {
Object.keys(tree).forEach(n => {
if(Object.keys(tree[n]).length) prune(tree[n], whitelist);
if(!whitelist.includes(n)) delete tree[n];
});
}
buildWhitelist(tree, find);
prune(tree, Array.from(found));
}
/**
* Recursively search network & build a tree
* @param host {string} - Point to scan from
* @param depth {number} - Current scanning depth
* @param blacklist {String[]} - Devices already discovered
* @returns Dicionary of discovered devices
*/
function scan(host, depth = 1, blacklist = [host]) {
if(depth > args['depth']) return {};
const localTargets = ns.scan(host).filter(target => !blacklist.includes(target));
blacklist = [...blacklist, ...localTargets];
return localTargets.reduce((acc, target) => {
const info = ns.getServer(target);
const verb = args['verbose'] ? ` (${info.hasAdminRights ? 'ROOTED' : `${info.requiredHackingSkill}|${info.numOpenPortsRequired}`})` : '';
const name = `${target}${verb}`;
acc[name] = scan(target, depth + 1, blacklist);
return acc;
}, {});
}
/**
* Iterate tree & print to screen
* @param tree {object} - Tree to parse
* @param tree {Object} - Tree to parse
* @param stats {Object} - Pool of stats to pull extra information from
* @param spacer {string} - Spacer text for tree formatting
*/
function render(tree, spacer = ' ') {
Object.keys(tree).forEach((key, i, arr) => {
function render(tree, stats, spacer = ' ') {
Object.keys(tree).forEach((device, i, arr) => {
const deviceStats = stats ? stats[device] : null;
const stat = deviceStats ? ` (${deviceStats.hasAdminRights ? 'ROOTED' : `${deviceStats.requiredHackingSkill}|${deviceStats.numOpenPortsRequired}`})` : '';
const last = i == arr.length - 1;
const branch = last ? '└─ ' : '├─ ';
ns.tprint(`${spacer}${branch}${key}`);
render(tree[key], spacer + (last ? ' ' : '| '));
ns.tprint(spacer + branch + device + stat);
render(tree[device], stats, spacer + (last ? ' ' : '| '));
});
}
// Run
const [devices, network] = scanNetwork(ns, args['device'], args['depth']);
const stats = devices.reduce((acc, d) => ({...acc, [d]: ns.getServer(d)}), {});
if(args['regex']) pruneTree(network, d => RegExp(args['regex']).test(d)); // Regex flag
else if(args['filter']) pruneTree(network, d => d == args['filter']); // Filter flag
if(args['rooted']) pruneTree(network, d => stats[d].hasAdminRights); // Rooted flag
else if(args['notRooted']) pruneTree(network, d => !stats[d].hasAdminRights); // Not rooted flag
ns.tprint(args['device']);
const found = scan(args['device']);
if(args['regex']) filter(found, args['regex'], true);
else if(args['filter']) filter(found, args['filter']);
render(found);
render(network, args['verbose'] ? stats : null);
ns.tprint('');
}
export function autocomplete(data) {
return [...data.servers];
}

View File

@ -136,8 +136,7 @@ export async function main(ns) {
'hacknet-manager.js',
'miner.js',
'network-graph.js',
'rootkit.js',
'vanguard.js'
'rootkit.js'
];
let args;
try {

View File

@ -1,54 +0,0 @@
import {ArgError, ArgParser} from './scripts/lib/arg-parser';
/**
* Weaken a device indefinitely.
* @params ns {NS} - BitBurner API
*/
export async function main(ns) {
// Setup
ns.disableLog('ALL');
let args, counter = 0, orgSecurity, security;
const historyLength = 17;
const messageHistory = Array(historyLength).fill('');
const argParser = new ArgParser('vanguard.js', 'Weaken a device indefinitely.', null, [
{name: 'device', desc: 'Device to weaken, defaults to the current machine', optional: true, default: ns.getHostname(), type: 'string'},
{name: 'limit', desc: 'Limit the number of times to run', flags: ['-l', '--limit'], default: Infinity, type: 'num'}
]);
try {
args = argParser.parse(ns.args);
orgSecurity = security = ns.getServerSecurityLevel(args['device']);
} catch(err) {
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
throw err;
}
/**
* Print header with logs
* @param message - message to append to logs
*/
function log(message) {
ns.clearLog();
ns.print('===================================================');
ns.print(`Vanguard: ${args['device']}`);
ns.print('===================================================');
ns.print(`Security: ${security}/${orgSecurity}`);
ns.print('===================================================');
if(message != null) messageHistory.push(message);
messageHistory.splice(0, messageHistory.length - historyLength);
messageHistory.forEach(m => ns.print(m));
}
// Run
log();
do {
security = ns.getServerSecurityLevel(args['device']);
log(`Attacking...`);
log(await ns.weaken(args['device']));
counter++;
} while (counter < args['limit']);
ns.print('Complete!');
}
export function autocomplete(data) {
return [...data.servers];
}