From dd9664d4ee0f2fa3f55a41bdd51cc7eaecc0ba8e Mon Sep 17 00:00:00 2001 From: monoid Date: Tue, 16 Aug 2022 23:18:41 +0900 Subject: [PATCH] script --- .eslintrc.js | 329 ++------------------------ .vscode/settings.json | 3 + build.js | 37 +++ package.json | 14 +- src/auto-hacking.ts | 15 ++ src/get-share-power.ts | 6 + src/hacknet-daemon.ts | 178 ++++++++++++++ src/lib/flag.ts | 455 ++++++++++++++++++++++++++++++++++++ src/lib/hack.ts | 95 ++++++++ src/lib/servers.ts | 71 ++++++ src/list-all-server.ts | 27 +++ src/list-purchase-server.ts | 13 ++ src/purchase-server.ts | 72 ++++++ src/server-status.ts | 79 +++++++ src/share-server.ts | 8 + src/util/assert.ts | 14 ++ src/util/range.ts | 9 + src/watcher.ts | 6 +- tsconfig.json | 1 - 19 files changed, 1107 insertions(+), 325 deletions(-) create mode 100644 build.js create mode 100644 src/auto-hacking.ts create mode 100644 src/get-share-power.ts create mode 100644 src/hacknet-daemon.ts create mode 100644 src/lib/flag.ts create mode 100644 src/lib/hack.ts create mode 100644 src/lib/servers.ts create mode 100644 src/list-all-server.ts create mode 100644 src/list-purchase-server.ts create mode 100644 src/purchase-server.ts create mode 100644 src/server-status.ts create mode 100644 src/share-server.ts create mode 100644 src/util/assert.ts create mode 100644 src/util/range.ts diff --git a/.eslintrc.js b/.eslintrc.js index 5479108..e636593 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,322 +16,23 @@ module.exports = { plugins: ["@typescript-eslint"], ignorePatterns: ['NetscriptDefinitions.d.ts', '*.js'], rules: { - "accessor-pairs": [ + "@typescript-eslint/array-type": ["error", { "default": "array-simple" }], + "@typescript-eslint/ban-ts-comment": ["off"], + "@typescript-eslint/explicit-member-accessibility": ["off"], + "@typescript-eslint/explicit-module-boundary-types": ["off"], + "@typescript-eslint/no-non-null-assertion": ["off"], + "@typescript-eslint/no-use-before-define": ["off"], + "@typescript-eslint/no-parameter-properties": ["off"], + "@typescript-eslint/no-unused-vars": [ "error", - { - setWithoutGet: true, - getWithoutSet: false, - }, + { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" } ], - "array-bracket-newline": ["off"], - "array-bracket-spacing": ["off"], - "array-callback-return": ["off"], - "array-element-newline": ["off"], - "arrow-body-style": ["off"], - "arrow-parens": ["off"], - "arrow-spacing": ["off"], - "block-scoped-var": ["off"], - "block-spacing": ["off"], - "brace-style": ["off"], - "callback-return": ["error"], - camelcase: ["off"], - "capitalized-comments": ["off"], - "class-methods-use-this": ["off"], - complexity: ["off"], - "consistent-return": ["off"], - "consistent-this": ["off"], - "constructor-super": ["error"], - curly: ["off"], - "default-case": ["off"], - "dot-notation": ["off"], - "eol-last": ["off"], - eqeqeq: ["off"], - "for-direction": ["error"], - "func-call-spacing": ["off"], - "func-name-matching": ["error"], - "func-names": ["off", "never"], - "func-style": ["off"], - "function-paren-newline": ["off"], - "getter-return": [ - "error", - { - allowImplicit: false, - }, - ], - "global-require": ["off"], - "guard-for-in": ["off"], - "handle-callback-err": ["error"], - "id-blacklist": ["error"], - "id-length": ["off"], - "id-match": ["error"], - indent: ["off"], - "indent-legacy": ["off"], - "init-declarations": ["off"], - "key-spacing": ["off"], - "keyword-spacing": ["off"], - "line-comment-position": ["off"], - "linebreak-style": [ - "off", // Line endings automatically converted to LF on git commit so probably shouldn't care about it here - ], - "lines-around-comment": ["off"], - "lines-around-directive": ["error"], - "lines-between-class-members": ["error"], - "max-depth": ["off"], - "max-len": ["off"], - "max-lines": ["off"], - "max-nested-callbacks": ["error"], - "max-params": ["off"], - "max-statements": ["off"], - "max-statements-per-line": ["off"], - "multiline-comment-style": ["off", "starred-block"], - "multiline-ternary": ["off", "never"], - "new-cap": ["off"], - "new-parens": ["off"], - "newline-after-var": ["off"], - "newline-before-return": ["off"], - "newline-per-chained-call": ["off"], - "no-alert": ["error"], - "no-array-constructor": ["error"], - "no-await-in-loop": ["off"], - "no-bitwise": ["off"], - "no-buffer-constructor": ["error"], - "no-caller": ["error"], - "no-case-declarations": ["error"], - "no-catch-shadow": ["error"], - "no-class-assign": ["error"], - "no-compare-neg-zero": ["error"], - "no-confusing-arrow": ["error"], - "no-console": ["off"], - "no-const-assign": ["error"], - "no-constant-condition": [ - "error", - { - checkLoops: false, - }, - ], - "no-continue": ["off"], - "no-control-regex": ["error"], - "no-debugger": ["error"], - "no-delete-var": ["error"], - "no-div-regex": ["error"], - "no-dupe-args": ["error"], - "no-dupe-class-members": ["error"], - "no-dupe-keys": ["error"], - "no-duplicate-case": ["error"], - "no-duplicate-imports": [ - "error", - { - includeExports: true, - }, - ], - "no-else-return": ["off"], - "no-empty": [ - "off", - { - allowEmptyCatch: false, - }, - ], - "no-empty-character-class": ["error"], - "no-empty-function": ["off"], - "no-empty-pattern": ["error"], - "no-eq-null": ["off"], - "no-ex-assign": ["off"], - "no-extra-boolean-cast": ["error"], - "no-extra-parens": ["off"], - "no-extra-semi": ["off"], - "no-eval": ["off"], - "no-extend-native": ["off"], - "no-extra-bind": ["error"], - "no-extra-label": ["error"], - "no-fallthrough": ["off"], - "no-floating-decimal": ["off"], - "no-func-assign": ["error"], - "no-global-assign": ["error"], - "no-implicit-coercion": ["off"], - "no-implicit-globals": ["error"], - "no-implied-eval": ["error"], - "no-inline-comments": ["off"], - "no-inner-declarations": ["off", "both"], - "no-invalid-regexp": ["error"], - "no-invalid-this": ["off"], - "no-irregular-whitespace": [ - "error", - { - skipStrings: false, - skipComments: false, - skipRegExps: false, - skipTemplates: false, - }, - ], - "no-iterator": ["error"], - "no-label-var": ["error"], - "no-labels": ["off"], - "no-lone-blocks": ["error"], - "no-lonely-if": ["off"], - "no-loop-func": ["off"], - "no-magic-numbers": ["off"], - "no-mixed-operators": ["off"], - "no-mixed-requires": ["error"], - "no-mixed-spaces-and-tabs": ["off"], - "no-multi-assign": ["off"], - "no-multi-spaces": ["off"], - "no-multi-str": ["error"], - "no-multiple-empty-lines": [ - "off", - { - max: 1, - }, - ], - "no-native-reassign": ["error"], - "no-negated-condition": ["off"], - "no-negated-in-lhs": ["error"], - "no-nested-ternary": ["off"], - "no-new": ["error"], - "no-new-func": ["error"], - "no-new-object": ["error"], - "no-new-require": ["error"], - "no-new-symbol": ["error"], - "no-new-wrappers": ["error"], - "no-octal": ["error"], - "no-octal-escape": ["error"], - "no-obj-calls": ["error"], - "no-param-reassign": ["off"], - "no-path-concat": ["error"], - "no-plusplus": ["off"], - "no-process-env": ["off"], - "no-process-exit": ["error"], - "no-proto": ["error"], - "no-prototype-builtins": ["off"], - "no-redeclare": ["off"], - "no-regex-spaces": ["error"], - "no-restricted-globals": ["error"], - "no-restricted-imports": ["error"], - "no-restricted-modules": ["error"], - "no-restricted-properties": [ - "off", - { - object: "console", - property: "log", - message: "'log' is too general, use an appropriate level when logging.", - }, - ], - "no-restricted-syntax": ["error"], - "no-return-assign": ["off"], - "no-return-await": ["error"], - "no-script-url": ["error"], - "no-self-assign": [ - "error", - { - props: false, - }, - ], - "no-self-compare": ["error"], - "no-sequences": ["error"], - "no-shadow": ["off"], - "no-shadow-restricted-names": ["error"], - "no-spaced-func": ["off"], - "no-sparse-arrays": ["error"], - "no-sync": ["error"], - "no-tabs": ["error"], - "no-template-curly-in-string": ["error"], - "no-ternary": ["off"], - "no-this-before-super": ["off"], - "no-throw-literal": ["error"], - "no-trailing-spaces": ["off"], - "no-undef": ["off"], - "no-undef-init": ["error"], - "no-undefined": ["off"], - "no-underscore-dangle": ["off"], - "no-unexpected-multiline": ["error"], - "no-unmodified-loop-condition": ["error"], - "no-unneeded-ternary": ["off"], - "no-unreachable": ["off"], - "no-unsafe-finally": ["error"], - "no-unsafe-negation": ["error"], - "no-unused-expressions": ["off"], - "no-unused-labels": ["error"], - "no-unused-vars": ["off"], - "no-use-before-define": ["off"], - "no-useless-call": ["off"], - "no-useless-computed-key": ["error"], - "no-useless-concat": ["off"], - "no-useless-constructor": ["error"], - "no-useless-escape": ["off"], - "no-useless-rename": [ - "error", - { - ignoreDestructuring: false, - ignoreExport: false, - ignoreImport: false, - }, - ], - "no-useless-return": ["off"], - "no-var": ["off"], - "no-void": ["off"], - "no-warning-comments": ["off"], - "no-whitespace-before-property": ["error"], - "no-with": ["error"], - "nonblock-statement-body-position": ["off", "below"], - "object-curly-newline": ["off"], - "object-curly-spacing": ["off"], - "object-property-newline": ["off"], - "object-shorthand": ["off"], - "one-var": ["off"], - "one-var-declaration-per-line": ["off"], - "operator-assignment": ["off"], - "operator-linebreak": ["off", "none"], - "padded-blocks": ["off"], - "padding-line-between-statements": ["error"], - "prefer-arrow-callback": ["off"], - "prefer-const": ["off"], - "prefer-destructuring": ["off"], - "prefer-numeric-literals": ["error"], - "prefer-promise-reject-errors": ["off"], - "prefer-reflect": ["off"], - "prefer-rest-params": ["off"], - "prefer-spread": ["off"], - "prefer-template": ["off"], - "quote-props": ["off"], - quotes: ["off"], - radix: ["off", "as-needed"], - "require-await": ["off"], - "require-jsdoc": ["off"], - "require-yield": ["error"], - "rest-spread-spacing": ["error", "never"], - semi: ["warn", "never"], - "semi-spacing": ["off"], - "semi-style": ["error", "last"], - "sort-imports": ["off"], - "sort-keys": ["off"], - "sort-vars": ["off"], - "space-before-blocks": ["off"], - "space-before-function-paren": ["off"], - "space-in-parens": ["off"], - "space-infix-ops": ["off"], - "space-unary-ops": ["off"], - "spaced-comment": ["off"], - strict: ["off"], - "switch-colon-spacing": [ - "error", - { - after: true, - before: false, - }, - ], - "symbol-description": ["error"], - "template-curly-spacing": ["error"], - "template-tag-spacing": ["error"], - "unicode-bom": ["error", "never"], - "use-isnan": ["error"], - "valid-jsdoc": ["off"], - "valid-typeof": ["error"], - "vars-on-top": ["off"], - "wrap-iife": ["error", "any"], - "wrap-regex": ["off"], - "yield-star-spacing": ["error", "before"], - yoda: ["error", "never"], - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/ban-ts-ignore": ["off"], + "@typescript-eslint/no-empty-function": ["off"], + "no-return-await": "error", + "require-await": "error", + "no-async-promise-executor": "error", + "no-constant-condition": ["off"] }, overrides: [ { diff --git a/.vscode/settings.json b/.vscode/settings.json index 7aab3b9..0848c12 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,9 +6,12 @@ "eslint.workingDirectories": ["./src"], "editor.codeActionsOnSave": {"source.fixAll.eslint": true}, + "bitburner.authToken": "MOG9GEp4MGcw4aupPiqC6C9kd8jtFR2JgWcuS9JMyJjYfRCv0sqWdoB1H0G7HMQY", + "bitburner.fileWatcher.enable": true, // Bitburner Extension Settings "bitburner.scriptRoot": "./dist/", + // Autosave Settings "files.autoSave": "off", diff --git a/build.js b/build.js new file mode 100644 index 0000000..daa687b --- /dev/null +++ b/build.js @@ -0,0 +1,37 @@ +const fs = require("fs"); +const path = require("path"); +const esbuild = require("esbuild"); +const yargs = require("yargs"); +yargs.scriptName("builder") + .usage("$0 [args]") + .command("watch","watch and build",(yargs)=>{ + + },(argv)=>{ + build({ + watch:true, + }); + }) + .command("build","build",(yargs)=>{}, + (argv)=>{ + build(); + }) + .help() + .argv; + +async function build(options){ + options = options || {}; + options.watch = options.watch || false; + let dir = fs.readdirSync("src"); + dir = dir.filter(x=>x.endsWith(".ts")); + + await esbuild.build({ + entryPoints:dir, + bundle:true, + write: true, + treeShaking: true, + watch: options.watch, + platform: "neutral", + target:"es2020", + outdir:"./dist", + }); +} \ No newline at end of file diff --git a/package.json b/package.json index e52426f..dc8b71b 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "watch": "npx tsc -w", - "lint": "eslint . --ext .ts", + "build": "node build.js", "defs": "node ./updateDefs.js" }, "repository": { @@ -18,12 +17,13 @@ }, "homepage": "https://github.com/SlyCedix/bitburner-typescript-template#readme", "devDependencies": { - "@types/lodash": "^4.14.178", "@types/node": "^16.4.3", + "@types/yargs": "^17.0.11", "@typescript-eslint/eslint-plugin": "^4.28.4", "@typescript-eslint/parser": "^4.28.4", - "eslint": "^7.31.0", - "ts-node": "^9.1.1", - "typescript": "^4.3.5" + "esbuild": "^0.15.3" + }, + "dependencies": { + "yargs": "^17.5.1" } -} \ No newline at end of file +} diff --git a/src/auto-hacking.ts b/src/auto-hacking.ts new file mode 100644 index 0000000..a965c93 --- /dev/null +++ b/src/auto-hacking.ts @@ -0,0 +1,15 @@ + +import { selectHackableServerList, hackServer } from "./lib/hack" +import {isRootedServer} from "./lib/servers" +import {NS} from "@ns" + +/** @param {NS} ns */ +// eslint-disable-next-line require-await +export async function main(ns: NS): Promise { + //ns.disableLog("ALL") + const servers = selectHackableServerList(ns).filter(x => !isRootedServer(x)) + servers.forEach(x => { + ns.tprint(`hack ${x.hostname}`) + hackServer(ns, x) + }) +} \ No newline at end of file diff --git a/src/get-share-power.ts b/src/get-share-power.ts new file mode 100644 index 0000000..e12cfd1 --- /dev/null +++ b/src/get-share-power.ts @@ -0,0 +1,6 @@ +import {NS} from "@ns" + +// eslint-disable-next-line require-await +export async function main(ns:NS):Promise{ + ns.tprint(ns.getSharePower()); +} \ No newline at end of file diff --git a/src/hacknet-daemon.ts b/src/hacknet-daemon.ts new file mode 100644 index 0000000..03004fa --- /dev/null +++ b/src/hacknet-daemon.ts @@ -0,0 +1,178 @@ +import {NS} from "@ns" +import { parse } from "./lib/flag"; +import {range} from "./util/range"; + +class Account{ + value: number; + timer?: NodeJS.Timer; + constructor(value: number){ + this.value = value; + this.timer = undefined; + } + startIncrement(ns:NS, rate: number): void{ + this.timer = setInterval(()=>{ + this.value += ns.hacknet.getNodeStats().production * rate; + },1000); + } + stopIncrement(): void{ + clearInterval(this.timer) + } +} + +export function getNodeProduction(level: number, ram: number, cores: number, mult: number, bitMult:number): number{ + const moneyGainPerLevel = 1.5; + const levelMult = level * moneyGainPerLevel; + const ramMult = Math.pow(1.035, ram - 1); + const coreMult = ((cores + 5) / 6); + return levelMult * ramMult * coreMult * mult * bitMult; +} + +interface HacknetNodeComponent { + level: T; + core: T; + ram: T; +} + +type ComponentKind = "level" | "core" | "ram"; + +export interface CEInfo { + upgradeCost : HacknetNodeComponent; + production : HacknetNodeComponent; + costEffective : HacknetNodeComponent; +} + +function getCostEffective(ns:NS, nodeIndex: number, n:number): CEInfo{ + const coreUpgradeCost = ns.hacknet.getCoreUpgradeCost(nodeIndex,n); + const ramUpgradeCost = ns.hacknet.getRamUpgradeCost(nodeIndex,n); + const levelUpgradeCost = ns.hacknet.getLevelUpgradeCost(nodeIndex,n); + const mult = ns.getHacknetMultipliers().production; + const bitMult = 1//ns.getBitNodeMultipliers().HacknetNodeMoney; + const stat = ns.hacknet.getNodeStats(nodeIndex); + + const coreProduction = getNodeProduction(stat.level, stat.ram, stat.cores + n, mult, bitMult) - stat.production; + const levelProduction = getNodeProduction(stat.level + n, stat.ram, stat.cores, mult, bitMult) - stat.production; + const ramProduction = getNodeProduction(stat.level, stat.ram * Math.pow(2,n), stat.cores, mult, bitMult) - stat.production; + + return { + upgradeCost:{ + core: coreUpgradeCost, + ram: ramUpgradeCost, + level: levelUpgradeCost, + }, + production:{ + core: coreProduction, + ram: ramProduction, + level: levelProduction, + }, + costEffective:{ + core: coreProduction/coreUpgradeCost, + ram: ramProduction/ramUpgradeCost, + level: levelProduction/levelUpgradeCost + } + } +} + +function createUpgradeAction(ns:NS, index: number, n: number, kind:ComponentKind):(account: Account)=>void{ + return (account: Account)=>{ + switch (kind) { + case "core": + account.value -= ns.hacknet.getCoreUpgradeCost(index,n); + ns.hacknet.upgradeCore(index,n) + break; + case "level": + account.value -= ns.hacknet.getLevelUpgradeCost(index,n); + ns.hacknet.upgradeLevel(index,n) + break; + case "ram": + account.value -= ns.hacknet.getRamUpgradeCost(index,n); + ns.hacknet.upgradeRam(index,n) + break; + } + } +} + +function getMinCENode(ns:NS, index: number, n: number):[number,ComponentKind]{ + const {costEffective: ce} = getCostEffective(ns,index,n); + if(ce.core < ce.level){ + if(ce.core < ce.ram){ + return [ce.core, "core"]; + } + else{ + return [ce.ram, "ram"]; + } + } + else { + if(ce.level < ce.ram){ + return [ce.level,"level"]; + } + else { + return [ce.ram,"ram"]; + } + } +} + +function* getIndexOfHackNode(ns:NS): Generator{ + for (const it of range(ns.hacknet.numNodes())) { + yield it; + } +} + +function getMinCEIndex(ns:NS, n: number):[number,number,ComponentKind]{ + let optimalC = Infinity; + let kind = ""; + let minIndex = 0; + for(const it of getIndexOfHackNode(ns)){ + const [c, k] = getMinCENode(ns,it,n); + if(c < optimalC){ + optimalC = c; + kind = k; + minIndex = it; + } + } + return [minIndex,optimalC,kind]; +} + +function getBuyNodeCE(ns:NS):{cost:number;production:number;costEffective: number}{ + const mult = ns.getHacknetMultipliers().production; + const bitMult = 1//ns.getBitNodeMultipliers().HacknetNodeMoney; + const cost = ns.hacknet.maxNumNodes() > ns.hacknet.numNodes() ? ns.hacknet.getPurchaseNodeCost() : Infinity; + const production = getNodeProduction(1,1,1,mult,bitMult); + return { + cost, + production, + costEffective: cost/production, + }; +} + +function cycle(ns:NS, account:Account): void{ + const chunkN = 1; + + const [upgradeNodeIndex,upgradeNodeCE,kind] = getMinCEIndex(ns,chunkN); + const {costEffective: buyNew} = getBuyNodeCE(ns); + if(upgradeNodeCE < buyNew){ + const action = createUpgradeAction(ns,upgradeNodeIndex,chunkN,kind); + action(account); + } + else { + ns.hacknet.purchaseNode() + } +} + +// eslint-disable-next-line require-await +export async function main(ns:NS):Promise{ + ns.disableLog("ALL") + const flag = parse(ns.args); + const iter = parseInt(flag.i ?? flag.iter ?? "10"); + const rate = parseFloat(flag.r ?? flag.rate ?? "0.5"); + const budget = parseInt(flag.b ?? flag.budget ?? "0"); + + const account = new Account(budget); + account.startIncrement(ns,rate); + ns.tail(); + for (let i = 0; i < iter; i++) { + ns.clearLog() + ns.print(`budget ${account.value}`); + cycle(ns,account); + await ns.sleep(1000); + } +} \ No newline at end of file diff --git a/src/lib/flag.ts b/src/lib/flag.ts new file mode 100644 index 0000000..d7d08bc --- /dev/null +++ b/src/lib/flag.ts @@ -0,0 +1,455 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +/** + * CLI flag parser. + * + * This module is browser compatible. + * + * @module + */ +import { assert } from "../util/assert"; + +/** The value returned from `parse`. */ +export interface Args { + /** Contains all the arguments that didn't have an option associated with + * them. */ + _: Array; + // deno-lint-ignore no-explicit-any + [key: string]: any; +} + +/** The options for the `parse` call. */ +export interface ParseOptions { + /** When `true`, populate the result `_` with everything before the `--` and + * the result `['--']` with everything after the `--`. Here's an example: + * + * ```ts + * // $ deno run example.ts -- a arg1 + * import { parse } from "./mod.ts"; + * console.dir(parse(Deno.args, { "--": false })); + * // output: { _: [ "a", "arg1" ] } + * console.dir(parse(Deno.args, { "--": true })); + * // output: { _: [], --: [ "a", "arg1" ] } + * ``` + * + * Defaults to `false`. + */ + "--"?: boolean; + + /** An object mapping string names to strings or arrays of string argument + * names to use as aliases. */ + alias?: Record; + + /** A boolean, string or array of strings to always treat as booleans. If + * `true` will treat all double hyphenated arguments without equal signs as + * `boolean` (e.g. affects `--foo`, not `-f` or `--foo=bar`) */ + boolean?: boolean | string | string[]; + + /** An object mapping string argument names to default values. */ + default?: Record; + + /** When `true`, populate the result `_` with everything after the first + * non-option. */ + stopEarly?: boolean; + + /** A string or array of strings argument names to always treat as strings. */ + string?: string | string[]; + + /** A string or array of strings argument names to always treat as arrays. + * Collectable options can be used multiple times. All values will be + * colelcted into one array. If a non-collectable option is used multiple + * times, the last value is used. */ + collect?: string | string[]; + + /** A string or array of strings argument names which can be negated + * by prefixing them with `--no-`, like `--no-config`. */ + negatable?: string | string[]; + + /** A function which is invoked with a command line parameter not defined in + * the `options` configuration object. If the function returns `false`, the + * unknown option is not added to `parsedArgs`. */ + unknown?: (arg: string, key?: string, value?: unknown) => unknown; +} + +interface Flags { + bools: Record; + strings: Record; + collect: Record; + negatable: Record; + unknownFn: (arg: string, key?: string, value?: unknown) => unknown; + allBools: boolean; +} + +interface NestedMapping { + [key: string]: NestedMapping | unknown; +} + +const { hasOwn } = Object; + +function get(obj: Record, key: string): T | undefined { + if (hasOwn(obj, key)) { + return obj[key]; + } +} + +function getForce(obj: Record, key: string): T { + const v = get(obj, key); + assert(v != null); + return v; +} + +function isNumber(x: unknown): boolean { + if (typeof x === "number") return true; + if (/^0x[0-9a-f]+$/i.test(String(x))) return true; + return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(String(x)); +} + +function hasKey(obj: NestedMapping, keys: string[]): boolean { + let o = obj; + keys.slice(0, -1).forEach((key) => { + o = (get(o, key) ?? {}) as NestedMapping; + }); + + const key = keys[keys.length - 1]; + return key in o; +} + +/** Take a set of command line arguments, optionally with a set of options, and + * return an object representing the flags found in the passed arguments. + * + * By default any arguments starting with `-` or `--` are considered boolean + * flags. If the argument name is followed by an equal sign (`=`) it is + * considered a key-value pair. Any arguments which could not be parsed are + * available in the `_` property of the returned object. + * + * ```ts + * import { parse } from "./mod.ts"; + * const parsedArgs = parse(Deno.args); + * ``` + * + * ```ts + * import { parse } from "./mod.ts"; + * const parsedArgs = parse(["--foo", "--bar=baz", "--no-qux", "./quux.txt"]); + * // parsedArgs: { foo: true, bar: "baz", qux: false, _: ["./quux.txt"] } + * ``` + */ +export function parse( + args: string[], + { + "--": doubleDash = false, + alias = {}, + boolean = false, + default: defaults = {}, + stopEarly = false, + string = [], + collect = [], + negatable = [], + unknown = (i: string): unknown => i, + }: ParseOptions = {}, +): Args { + const flags: Flags = { + bools: {}, + strings: {}, + unknownFn: unknown, + allBools: false, + collect: {}, + negatable: {}, + }; + + if (boolean !== undefined) { + if (typeof boolean === "boolean") { + flags.allBools = !!boolean; + } else { + const booleanArgs = typeof boolean === "string" ? [boolean] : boolean; + + for (const key of booleanArgs.filter(Boolean)) { + flags.bools[key] = true; + } + } + } + + const aliases: Record = {}; + if (alias !== undefined) { + for (const key in alias) { + const val = getForce(alias, key); + if (typeof val === "string") { + aliases[key] = [val]; + } else { + aliases[key] = val; + } + for (const alias of getForce(aliases, key)) { + aliases[alias] = [key].concat(aliases[key].filter((y) => alias !== y)); + } + } + } + + if (string !== undefined) { + const stringArgs = typeof string === "string" ? [string] : string; + + for (const key of stringArgs.filter(Boolean)) { + flags.strings[key] = true; + const alias = get(aliases, key); + if (alias) { + for (const al of alias) { + flags.strings[al] = true; + } + } + } + } + + if (collect !== undefined) { + const collectArgs = typeof collect === "string" ? [collect] : collect; + + for (const key of collectArgs.filter(Boolean)) { + flags.collect[key] = true; + const alias = get(aliases, key); + if (alias) { + for (const al of alias) { + flags.collect[al] = true; + } + } + } + } + + if (negatable !== undefined) { + const negatableArgs = typeof negatable === "string" + ? [negatable] + : negatable; + + for (const key of negatableArgs.filter(Boolean)) { + flags.negatable[key] = true; + const alias = get(aliases, key); + if (alias) { + for (const al of alias) { + flags.negatable[al] = true; + } + } + } + } + + const argv: Args = { _: [] }; + + function argDefined(key: string, arg: string): boolean { + return ( + (flags.allBools && /^--[^=]+$/.test(arg)) || + get(flags.bools, key) || + !!get(flags.strings, key) || + !!get(aliases, key) + ); + } + + function setKey( + obj: NestedMapping, + name: string, + value: unknown, + collect = true, + ): void { + let o = obj; + const keys = name.split("."); + keys.slice(0, -1).forEach(function (key): void { + if (get(o, key) === undefined) { + o[key] = {}; + } + o = get(o, key) as NestedMapping; + }); + + const key = keys[keys.length - 1]; + const collectable = collect && !!get(flags.collect, name); + + if (!collectable) { + o[key] = value; + } else if (get(o, key) === undefined) { + o[key] = [value]; + } else if (Array.isArray(get(o, key))) { + (o[key] as unknown[]).push(value); + } else { + o[key] = [get(o, key), value]; + } + } + + function setArg( + key: string, + val: unknown, + arg: string | undefined = undefined, + collect?: boolean, + ): void { + if (arg && flags.unknownFn && !argDefined(key, arg)) { + if (flags.unknownFn(arg, key, val) === false) return; + } + + const value = !get(flags.strings, key) && isNumber(val) ? Number(val) : val; + setKey(argv, key, value, collect); + + const alias = get(aliases, key); + if (alias) { + for (const x of alias) { + setKey(argv, x, value, collect); + } + } + } + + function aliasIsBoolean(key: string): boolean { + return getForce(aliases, key).some( + (x) => typeof get(flags.bools, x) === "boolean", + ); + } + + let notFlags: string[] = []; + + // all args after "--" are not parsed + if (args.includes("--")) { + notFlags = args.slice(args.indexOf("--") + 1); + args = args.slice(0, args.indexOf("--")); + } + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (/^--.+=/.test(arg)) { + const m = arg.match(/^--([^=]+)=(.*)$/s); + assert(m != null); + const [, key, value] = m; + + if (flags.bools[key]) { + const booleanValue = value !== "false"; + setArg(key, booleanValue, arg); + } else { + setArg(key, value, arg); + } + } else if ( + /^--no-.+/.test(arg) && get(flags.negatable, arg.replace(/^--no-/, "")) + ) { + const m = arg.match(/^--no-(.+)/); + assert(m != null); + setArg(m[1], false, arg, false); + } else if (/^--.+/.test(arg)) { + const m = arg.match(/^--(.+)/); + assert(m != null); + const [, key] = m; + const next = args[i + 1]; + if ( + next !== undefined && + !/^-/.test(next) && + !get(flags.bools, key) && + !flags.allBools && + (get(aliases, key) ? !aliasIsBoolean(key) : true) + ) { + setArg(key, next, arg); + i++; + } else if (/^(true|false)$/.test(next)) { + setArg(key, next === "true", arg); + i++; + } else { + setArg(key, get(flags.strings, key) ? "" : true, arg); + } + } else if (/^-[^-]+/.test(arg)) { + const letters = arg.slice(1, -1).split(""); + + let broken = false; + for (let j = 0; j < letters.length; j++) { + const next = arg.slice(j + 2); + + if (next === "-") { + setArg(letters[j], next, arg); + continue; + } + + if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) { + setArg(letters[j], next.split(/=(.+)/)[1], arg); + broken = true; + break; + } + + if ( + /[A-Za-z]/.test(letters[j]) && + /-?\d+(\.\d*)?(e-?\d+)?$/.test(next) + ) { + setArg(letters[j], next, arg); + broken = true; + break; + } + + if (letters[j + 1] && letters[j + 1].match(/\W/)) { + setArg(letters[j], arg.slice(j + 2), arg); + broken = true; + break; + } else { + setArg(letters[j], get(flags.strings, letters[j]) ? "" : true, arg); + } + } + + const [key] = arg.slice(-1); + if (!broken && key !== "-") { + if ( + args[i + 1] && + !/^(-|--)[^-]/.test(args[i + 1]) && + !get(flags.bools, key) && + (get(aliases, key) ? !aliasIsBoolean(key) : true) + ) { + setArg(key, args[i + 1], arg); + i++; + } else if (args[i + 1] && /^(true|false)$/.test(args[i + 1])) { + setArg(key, args[i + 1] === "true", arg); + i++; + } else { + setArg(key, get(flags.strings, key) ? "" : true, arg); + } + } + } else { + if (!flags.unknownFn || flags.unknownFn(arg) !== false) { + argv._.push(flags.strings["_"] ?? !isNumber(arg) ? arg : Number(arg)); + } + if (stopEarly) { + argv._.push(...args.slice(i + 1)); + break; + } + } + } + + for (const [key, value] of Object.entries(defaults)) { + if (!hasKey(argv, key.split("."))) { + setKey(argv, key, value); + + if (aliases[key]) { + for (const x of aliases[key]) { + setKey(argv, x, value); + } + } + } + } + + for (const key of Object.keys(flags.bools)) { + if (!hasKey(argv, key.split("."))) { + const value = get(flags.collect, key) ? [] : false; + setKey( + argv, + key, + value, + false, + ); + } + } + + for (const key of Object.keys(flags.strings)) { + if (!hasKey(argv, key.split(".")) && get(flags.collect, key)) { + setKey( + argv, + key, + [], + false, + ); + } + } + + if (doubleDash) { + argv["--"] = []; + for (const key of notFlags) { + argv["--"].push(key); + } + } else { + for (const key of notFlags) { + argv._.push(key); + } + } + + return argv; +} diff --git a/src/lib/hack.ts b/src/lib/hack.ts new file mode 100644 index 0000000..2e8ad8e --- /dev/null +++ b/src/lib/hack.ts @@ -0,0 +1,95 @@ +import { NS, Server } from '@ns' +import {selectAllServerList, ServerInfo} from "./servers" + +export type Hackability = "hackable" | "rooted" | "impossible" + +export function getHackability(ns: NS,hostname:string): Hackability{ + if (ns.hasRootAccess(hostname)) { + return "rooted"; + } + if (ns.getServerRequiredHackingLevel(hostname) > ns.getHackingLevel() || + ns.getServerNumPortsRequired(hostname) > hackablePorts(ns)) { + return "impossible"; + } + return "hackable"; +} +export function getHackabilityServer(ns: NS,server:Server): Hackability{ + if (server.hasAdminRights) { + return "rooted"; + } + if (server.requiredHackingSkill > ns.getHackingLevel() || + server.numOpenPortsRequired > hackablePorts(ns)) { + return "impossible"; + } + return "hackable"; +} + +export function getLockSymbol(k:Hackability):string{ + switch (k) { + case "rooted": + return "🔓" + case "hackable": + return "🔐" + case "impossible": + return "🔒" + } +} + +type Hack = { + file: string; + exec: (ns: NS, target:string)=> void; +}; + +export const hacks: Hack[] = [ + { file: 'BruteSSH.exe', exec: (ns: NS, target: string) => ns.brutessh(target) }, + { file: 'FTPCrack.exe', exec: (ns: NS, target: string) => ns.ftpcrack(target) }, + { file: 'relaySMTP.exe', exec: (ns: NS, target: string) => ns.relaysmtp(target) }, + { file: 'HTTPWorm.exe', exec: (ns: NS, target: string) => ns.httpworm(target) }, + { file: 'SQLInject.exe', exec: (ns: NS, target: string) => ns.sqlinject(target) }, +] + +/** + * @param {NS} ns + */ +export function hackablePorts(ns: NS): number { + return hacks.map(x=>(ns.fileExists(x.file) ? 1 : 0)).reduce((x,y)=>x+y) +} + +export function isHackableServer(ns: NS, target: Server): boolean{ + return target.numOpenPortsRequired <= hackablePorts(ns) && + target.hackDifficulty <= ns.getHackingLevel() +} +export function selectHackableServerList(ns: NS): ServerInfo[]{ + return selectAllServerList(ns).filter(x=>{ + return isHackableServer(ns,x) + }) +} + +export function hackServer(ns:NS,target:Server); +export function hackServer(ns:NS,target:string); +export function hackServer(ns:NS,target:string|Server):void{ + const hostname = typeof target === "string" ? target : target.hostname + hacks.forEach(x=>{ + if(ns.fileExists(x.file,"home")){ + x.exec(ns, hostname) + } + }) + ns.nuke(hostname) +} + +/** @param {NS} ns */ +// eslint-disable-next-line require-await +export async function main(ns: NS):Promise { + if(ns.args.length == 0){ + ns.tprint("argument required") + ns.tprint("run cmd [target]") + return + } + const target = ns.args[0] + hackServer(target) +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function autocomplete(data : ServerData, args : string[]) : string[] { + return [...data.servers] +} \ No newline at end of file diff --git a/src/lib/servers.ts b/src/lib/servers.ts new file mode 100644 index 0000000..0b29b2f --- /dev/null +++ b/src/lib/servers.ts @@ -0,0 +1,71 @@ +import { NS, Server } from '@ns' + +/** @param {NS} ns */ +export async function main(ns: NS): Promise { + if (ns.args.length == 0) { + ns.tprint("argument required") + ns.tprint("search all server and print to json") + ns.tprint("run cmd [filePath]") + ns.exit() + } + const filePath = ns.args[0] + const collectedData = selectAllServerList() + const buf = JSON.stringify(collectedData, undefined, 2) + await ns.write(filePath, buf, "w") +} + +export interface ServerInfo extends Server{ + relHost:string[]; +} + +/** + * @param {NS} ns + * @param func + */ +export function visitAllServerList(ns: NS, func:(data: ServerInfo)=>void):void { + /** @type {string[]} */ + const queue = ["home"] + /** @type {Set} */ + const visited = new Set() + + //breadth first search + while (queue.length > 0) { + const vHost = queue.pop() + const data = getServersInfo(ns, vHost) + + func(data) + + visited.add(vHost) + for (const h of data.relHost) { + if (!visited.has(h)) { + queue.push(h) + } + } + } +} +/**@param {NS} ns */ +export function selectAllServerList(ns: NS): ServerInfo[] { + const collectedData: ServerInfo[] = [] + visitAllServerList(ns, (data) => { + collectedData.push(data) + }) + return collectedData +} + +export function isRootedServer(server:Server):boolean{ + return server.hasAdminRights +} + +export function selectRootedServerList(ns:NS): ServerInfo[]{ + return selectAllServerList(ns).filter(isRootedServer) +} + +/** @param {NS} ns + * @param {string} host + * @return {Server & {relHost: string[]}} + */ +export function getServersInfo(ns: NS, host: string): ServerInfo { + const neighbor = ns.scan(host) + const info = ns.getServer(host) + return { ...info, relHost: neighbor } +} \ No newline at end of file diff --git a/src/list-all-server.ts b/src/list-all-server.ts new file mode 100644 index 0000000..7133109 --- /dev/null +++ b/src/list-all-server.ts @@ -0,0 +1,27 @@ +import {NS} from "@ns" +import {getHackability,getLockSymbol} from "./lib/hack"; + +/** @param {NS} ns */ +// eslint-disable-next-line require-await +export async function main(ns: NS): Promise { + findServer(ns, 'home', 'home', 1); +} + +/** @param {NS} ns + * @param {string} startServer + * @param {string} targetServer + * @param {number} depth +*/ +function findServer(ns: NS, startServer: string, targetServer: string, depth: number): void { + const servers = ns.scan(targetServer) + .filter((server) => server !== startServer); + servers.forEach((server) => { + const lock = getHackability(ns, server); + const lock_symbol = getLockSymbol(lock); + const info = ns.getServer(server); + ns.tprint(`😹${'>'.repeat(depth)} ${lock_symbol} ${server} ${info.backdoorInstalled ? '✅': '❌'}`); + if (lock !== "impossible") { + findServer(ns, targetServer, server, depth + 1); + } + }); +} \ No newline at end of file diff --git a/src/list-purchase-server.ts b/src/list-purchase-server.ts new file mode 100644 index 0000000..c3e3f67 --- /dev/null +++ b/src/list-purchase-server.ts @@ -0,0 +1,13 @@ +import {NS} from "@ns" +import {parse} from "lib/flag" + +/** @param {NS} ns */ +// eslint-disable-next-line require-await +export async function main(ns: NS):Promise { + const flag = parse(ns.args); + if(flag.help || flag.h){ + ns.tprint(`* list all purchased server`); + ns.exit(); + } + ns.tprint(ns.getPurchasedServers().join("\n")); +} \ No newline at end of file diff --git a/src/purchase-server.ts b/src/purchase-server.ts new file mode 100644 index 0000000..7a22a69 --- /dev/null +++ b/src/purchase-server.ts @@ -0,0 +1,72 @@ +import { NS } from "@ns" +import {parse} from "./lib/flag" +import {range} from "./util/range"; + + +/** @param {NS} ns */ +export async function main(ns: NS): Promise { + const flag = parse(ns.args); + if(flag.h || flag.help){ + ns.tprint("script : purchase server") + ns.tprint(""); + ns.exit() + } + if(Boolean(flag.i) || Boolean(flag.interactive)){ + const ramLimit = ns.getPurchasedServerMaxRam() + const choices = [...range(3,20)].map(x=>Math.pow(2,x)).filter(x=>x <= ramLimit).map((x)=>{ + const cost = ns.getPurchasedServerCost(x); + return `${x}GB (${ns.nFormat(cost,"$0.000a")})` + }); + const choice = await ns.prompt("which server do you purchase?",{ + type:"select", + choices + }); + if(choice === ""){ + ns.tprint("canceled"); + ns.exit(); + return; + } + const gb = parseInt(/^(\d+)GB/.exec(choice)[1]); + ns.tprint("you select ",gb,"GB"); + const hostname = await ns.prompt("name your server",{type:"text"}); + if(hostname === ""){ + ns.tprint("canceled"); + ns.exit(); + return; + } + const p = await ns.prompt( + `do you want to purchase server ${gb}GB(${ + ns.nFormat(ns.getPurchasedServerCost(gb),"$0.000a")})?`, + { type: "boolean" }); + if (p) { + const l = ns.purchaseServer(hostname, ram) + ns.tprint(l, " purchased"); + } + ns.exit() + return; + } + + if ((flag.n || flag.name) && (flag.s || flag.size)) { + ns.tprint("argument needed") + ns.tprint("run cmd -n [hostname] -s [ram(GB)]") + ns.tprint("run cmd --name [hostname] --size [ram(GB)]") + ns.exit() + return; + } + + const hostname = flag.n ?? flag.name + const ram = parseInt(flag.s ?? flag.size) + if(isNaN(ram)){ + ns.tprint("size must be integer!"); + ns.exit(); + return; + } + const f = ns.getPurchasedServerCost(ram) + const ff = ns.nFormat(f, "($ 0.00 a)") + const p = await ns.prompt("required : " + ff + "\ndo you want to purchase server " + ram + "GB?", + { type: "boolean" }) + if (p) { + const l = ns.purchaseServer(hostname, ram) + ns.tprint(l, " purchased") + } +} \ No newline at end of file diff --git a/src/server-status.ts b/src/server-status.ts new file mode 100644 index 0000000..664656a --- /dev/null +++ b/src/server-status.ts @@ -0,0 +1,79 @@ +import {NS} from "@ns"; +import {hackablePorts, getHackability, getLockSymbol} from "./lib/hack"; +import {parse} from "./lib/flag"; + + +type ReportOption = { + detail?: boolean; +} +/* + * Utility functions that report serverStatus + * and Hackability + * @param {NS} ns + */ +// eslint-disable-next-line require-await +export async function main(ns: NS): Promise { + const flag = parse(ns.args); + if (flag._.length == 0) { + ns.tprint("argumented required"); + return; + } + const hostname = flag._[0]; + const detail = Boolean(flag.d) || Boolean(flag.detail); + serverReport(ns, hostname,{ + detail: detail, + }); +} +/** + * @param {NS} ns + * @param {string} hostname + */ +export function serverReport(ns: NS, hostname: string, options?:ReportOption):void { + options ??= {}; + const serverLock = getHackability(ns, hostname); + ns.tprint(`${getLockSymbol(serverLock)} ${hostname}`); + if(options.detail){ + const server = ns.getServer(hostname); + const msg = ['', + `hostname : ${server.hostname}(${server.ip})`, + `🛡️${ns.nFormat(server.hackDifficulty,"0[.]000")}/${server.minDifficulty}(${server.baseDifficulty})`, + `💸${ns.nFormat(server.moneyAvailable,"$0.000a")}/${ns.nFormat(server.moneyMax,"$0.000a")}`, + `💾${server.ramUsed}GB/${server.maxRam}GB`, + `backdoorInstalled \t: ${server.backdoorInstalled ? `🚪` : `❌`}`, + `cpu \t\t\t: ${server.cpuCores}`, + `growth \t\t\t: ${server.serverGrowth}`, + `organization name\t: ${server.organizationName}`, + `purchased \t\t: ${server.purchasedByPlayer}`, + `required hacking skill\t: ${server.requiredHackingSkill}`, + `ports \t\t\t: ${server.openPortCount}/${server.numOpenPortsRequired}` + ].join("\n"); + ns.tprint(msg); + } + else{ + if (serverLock == "rooted") { + ns.tprint(`🛡️${Math.round(ns.getServerSecurityLevel(hostname))}/${ns.getServerMinSecurityLevel(hostname)}`); + ns.tprint(`💸${ns.nFormat(ns.getServerMoneyAvailable(hostname), "$0.000a")}/${ns.nFormat(ns.getServerMaxMoney(hostname), "$0.000a")}`); + } else { + ns.tprint(`Hack Level: ${ns.getServerRequiredHackingLevel(hostname)}`); + ns.tprint(`Ports: ${ns.getServerNumPortsRequired(hostname)}`); + } + } +} +/** + * @param {NS} ns + */ +export function serverHackStatus(ns: NS, server: string): string { + if (ns.hasRootAccess(server)) { + return "🔓"; + } + if (ns.getServerRequiredHackingLevel(server) > ns.getHackingLevel() || + ns.getServerNumPortsRequired(server) > hackablePorts(ns)) { + return "🔒"; + } + return "🔐"; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function autocomplete(data : ServerData, args : string[]) : string[] { + return [...data.servers] +} \ No newline at end of file diff --git a/src/share-server.ts b/src/share-server.ts new file mode 100644 index 0000000..074a56b --- /dev/null +++ b/src/share-server.ts @@ -0,0 +1,8 @@ +import {NS} from "@ns" + +// eslint-disable-next-line require-await +export async function main(ns:NS):Promise{ + while(true){ + await ns.share() + } +} \ No newline at end of file diff --git a/src/util/assert.ts b/src/util/assert.ts new file mode 100644 index 0000000..d6ba9e8 --- /dev/null +++ b/src/util/assert.ts @@ -0,0 +1,14 @@ +import assert from "assert"; + +export class AssertionError extends Error{ + constructor(msg:string){ + super(msg); + this.name = "AssertionError"; + } +} + +export function assert(expr: unknown, msg=""): asserts expr{ + if(!expr){ + throw new AssertionError(msg) + } +} \ No newline at end of file diff --git a/src/util/range.ts b/src/util/range.ts new file mode 100644 index 0000000..377437c --- /dev/null +++ b/src/util/range.ts @@ -0,0 +1,9 @@ +export function* range(end: number): Generator; +export function* range(begin:number,end:number): Generator; +export function* range(arg1: number, arg2?: number): Generator{ + const begin = arg2 ? arg1 : 0; + const end = arg2 ?? arg1; + for(let i = begin; i { const files = ns.ls('home', '.js') for (const file of files) { const contents = ns.read(file) - hashes[file] = getHash(contents) + hashes[file] = getHash(contents as string) } - + // eslint-disable-next-line no-constant-condition while (true) { const files = ns.ls('home', '.js') for (const file of files) { const contents = ns.read(file) - const hash = getHash(contents) + const hash = getHash(contents as string) if (hash != hashes[file]) { ns.tprintf(`INFO: Detected change in ${file}`) diff --git a/tsconfig.json b/tsconfig.json index 86e6ba7..15556b2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,6 @@ "noImplicitThis": true, "esModuleInterop": true, "inlineSourceMap": true, - "sourceRoot": "http://localhost:8000/sources/", "strict": true, "rootDir": "src/", "outDir": "dist/",