From 607396476822f7205266ee496980d8e3ce3bb868 Mon Sep 17 00:00:00 2001 From: Michael Ficocelli Date: Tue, 3 Feb 2026 06:40:36 -0500 Subject: [PATCH] DARKNET: Darkweb Expansion Project & Bitnode (#2139) This is BN15. It is a really big change; see the PR for all the details. --- assets/Steam/achievements/icons/BN15+.svg | 69 + assets/Steam/achievements/icons/SF15.1.svg | 69 + .../achievements/icons/darknet-backdoor.svg | 69 + dist/icons/achievements/BN15+.svg | 69 + dist/icons/achievements/SF15.1.svg | 69 + dist/icons/achievements/darknet-backdoor.svg | 69 + markdown/bitburner._valueof.md | 2 +- markdown/bitburner.cacheresult.md | 16 + markdown/bitburner.codingcontract.attempt.md | 2 +- .../bitburner.codingcontract.getcontract.md | 2 +- ...itburner.codingcontract.getcontracttype.md | 2 +- markdown/bitburner.codingcontract.getdata.md | 2 +- ...bitburner.codingcontract.getdescription.md | 2 +- ...ner.codingcontract.getnumtriesremaining.md | 2 +- markdown/bitburner.darknet.authenticate.md | 94 ++ .../bitburner.darknet.connecttosession.md | 80 + markdown/bitburner.darknet.getblockedram.md | 60 + ...bitburner.darknet.getdarknetinstability.md | 23 + markdown/bitburner.darknet.getdepth.md | 62 + .../bitburner.darknet.getserverauthdetails.md | 60 + ....darknet.getserverrequiredcharismalevel.md | 62 + ...itburner.darknet.getstasislinkedservers.md | 60 + .../bitburner.darknet.getstasislinklimit.md | 23 + markdown/bitburner.darknet.heartbleed.md | 80 + ...bitburner.darknet.induceservermigration.md | 62 + markdown/bitburner.darknet.isdarknetserver.md | 62 + markdown/bitburner.darknet.labradar.md | 21 + markdown/bitburner.darknet.labreport.md | 21 + markdown/bitburner.darknet.md | 328 ++++ .../bitburner.darknet.memoryreallocation.md | 62 + markdown/bitburner.darknet.nextmutation.md | 33 + markdown/bitburner.darknet.opencache.md | 76 + markdown/bitburner.darknet.packetcapture.md | 62 + markdown/bitburner.darknet.phishingattack.md | 27 + markdown/bitburner.darknet.probe.md | 62 + markdown/bitburner.darknet.promotestock.md | 60 + markdown/bitburner.darknet.setstasislink.md | 64 + .../bitburner.darknet.unleashstormseed.md | 25 + ...ner.darknetformulas.getauthenticatetime.md | 86 ++ ...knetformulas.getexpectedramblockremoved.md | 86 ++ ...urner.darknetformulas.getheartbleedtime.md | 86 ++ markdown/bitburner.darknetformulas.md | 62 + markdown/bitburner.darknetinstability.md | 16 + markdown/bitburner.darknetresponsecode.md | 14 + markdown/bitburner.darknetresponsecodetype.md | 35 + markdown/bitburner.darknetresult.md | 14 + ...ner.darknetserverdata.backdoorinstalled.md | 13 + .../bitburner.darknetserverdata.blockedram.md | 13 + .../bitburner.darknetserverdata.cpucores.md | 13 + markdown/bitburner.darknetserverdata.depth.md | 13 + .../bitburner.darknetserverdata.difficulty.md | 13 + ...burner.darknetserverdata.hasadminrights.md | 13 + ...tburner.darknetserverdata.hasstasislink.md | 13 + .../bitburner.darknetserverdata.hostname.md | 13 + markdown/bitburner.darknetserverdata.ip.md | 13 + ...tburner.darknetserverdata.isconnectedto.md | 13 + ...itburner.darknetserverdata.isstationary.md | 13 + ...er.darknetserverdata.logtrafficinterval.md | 13 + .../bitburner.darknetserverdata.maxram.md | 13 + markdown/bitburner.darknetserverdata.md | 400 +++++ .../bitburner.darknetserverdata.modelid.md | 13 + ...rner.darknetserverdata.passwordhintdata.md | 13 + ...ner.darknetserverdata.purchasedbyplayer.md | 13 + .../bitburner.darknetserverdata.ramused.md | 13 + ...darknetserverdata.requiredcharismaskill.md | 13 + ...er.darknetserverdata.staticpasswordhint.md | 13 + markdown/bitburner.formulas.dnet.md | 13 + markdown/bitburner.formulas.md | 19 + markdown/bitburner.heartbleedoptions.md | 17 + markdown/bitburner.locationnameenumtype.md | 1 + markdown/bitburner.md | 117 ++ markdown/bitburner.ns.dnet.md | 18 + markdown/bitburner.ns.exec.md | 2 +- markdown/bitburner.ns.fileexists.md | 2 +- markdown/bitburner.ns.getgrowtime.md | 4 +- markdown/bitburner.ns.gethacktime.md | 4 +- markdown/bitburner.ns.getrunningscript.md | 2 +- markdown/bitburner.ns.getscriptlogs.md | 2 +- markdown/bitburner.ns.getserver.md | 12 +- markdown/bitburner.ns.getweakentime.md | 4 +- .../bitburner.ns.growthanalyzesecurity.md | 2 +- markdown/bitburner.ns.hackanalyzesecurity.md | 4 +- markdown/bitburner.ns.isrunning.md | 2 +- markdown/bitburner.ns.kill_1.md | 2 +- markdown/bitburner.ns.killall.md | 4 +- markdown/bitburner.ns.md | 29 +- markdown/bitburner.ns.ps.md | 2 +- markdown/bitburner.ns.rm.md | 2 +- markdown/bitburner.ns.scan.md | 4 +- markdown/bitburner.ns.scp.md | 1 + markdown/bitburner.ns.spawn.md | 2 + markdown/bitburner.ns.wget.md | 2 +- markdown/bitburner.nsenums.md | 3 +- markdown/bitburner.serverauthdetails.md | 22 + markdown/bitburner.userinterface.opentail.md | 2 +- ...bitburner.userinterface.settailfontsize.md | 2 +- src/Achievements/AchievementData.json | 15 + src/Achievements/Achievements.ts | 18 +- src/Achievements/Types.ts | 1 + src/Augmentation/Augmentations.ts | 172 ++- src/Augmentation/Enums.ts | 15 + src/BitNode/BitNode.tsx | 85 +- src/BitNode/BitNodeMultipliers.ts | 6 + src/BitNode/Constants.ts | 2 +- src/BitNode/ui/BitverseRoot.tsx | 2 +- src/Constants.ts | 1 + src/DarkNet/Constants.ts | 11 + src/DarkNet/DWRoot.tsx | 11 + src/DarkNet/Enums.ts | 90 ++ src/DarkNet/controllers/NetworkGenerator.ts | 255 ++++ src/DarkNet/controllers/NetworkMovement.ts | 397 +++++ src/DarkNet/controllers/ServerGenerator.ts | 706 +++++++++ src/DarkNet/effects/SaveLoad.ts | 30 + src/DarkNet/effects/authentication.ts | 223 +++ src/DarkNet/effects/cacheFiles.ts | 141 ++ src/DarkNet/effects/effects.ts | 295 ++++ src/DarkNet/effects/labyrinth.ts | 502 +++++++ src/DarkNet/effects/offlineServerHandling.ts | 167 +++ src/DarkNet/effects/phishing.ts | 66 + src/DarkNet/effects/ramblock.ts | 105 ++ src/DarkNet/effects/webstorm.ts | 65 + src/DarkNet/models/DarknetServerOptions.ts | 170 +++ src/DarkNet/models/DarknetState.ts | 126 ++ src/DarkNet/models/dictionaryData.ts | 388 +++++ src/DarkNet/models/hintNotes.ts | 14 + src/DarkNet/models/packetSniffing.ts | 238 +++ src/DarkNet/ui/LabyrinthSummary.tsx | 195 +++ src/DarkNet/ui/NetworkDisplayWrapper.tsx | 368 +++++ src/DarkNet/ui/PasswordPrompt.tsx | 186 +++ src/DarkNet/ui/ServerDetailsModal.tsx | 115 ++ src/DarkNet/ui/ServerIcon.ts | 86 ++ src/DarkNet/ui/ServerStatusBox.tsx | 71 + src/DarkNet/ui/ServerSummary.tsx | 151 ++ src/DarkNet/ui/dnetStyles.ts | 161 ++ src/DarkNet/ui/networkCanvas.ts | 90 ++ src/DarkNet/ui/uiUtilities.tsx | 41 + src/DarkNet/utils/darknetAuthUtils.ts | 64 + src/DarkNet/utils/darknetNetworkUtils.ts | 103 ++ src/DarkNet/utils/darknetServerUtils.ts | 21 + src/DarkWeb/DarkWeb.tsx | 6 + src/DarkWeb/DarkWebItems.ts | 6 + src/DevMenu.tsx | 2 + src/DevMenu/ui/DarknetDev.tsx | 303 ++++ src/DevMenu/ui/ServersDev.tsx | 8 +- src/Documentation/doc/en/index.md | 1 + .../doc/en/programming/darknet.md | 253 ++++ src/Documentation/pages.ts | 140 +- .../ui/DocumentationAutocomplete.tsx | 58 +- src/Faction/FactionHelpers.tsx | 11 +- src/Go/boardAnalysis/goAI.ts | 8 +- src/Go/boardAnalysis/patternMatching.ts | 2 +- src/Hacking.ts | 2 + src/Hacknet/HacknetServer.ts | 4 +- src/Literature/Enums.ts | 12 + src/Literature/Literatures.tsx | 114 ++ src/Locations/Enums.ts | 1 + src/Locations/Locations.ts | 2 +- src/Locations/data/LocationsMetadata.ts | 5 + src/Locations/ui/SpecialLocation.tsx | 60 +- src/Locations/ui/TorButton.tsx | 11 +- src/Milestones/Milestone.ts | 2 +- src/Milestones/Milestones.ts | 29 +- src/Milestones/ui/MilestonesRoot.tsx | 2 +- src/Netscript/NetscriptHelpers.tsx | 53 +- src/Netscript/RamCostGenerator.ts | 34 + src/Netscript/TypeAssertion.ts | 19 + src/Netscript/killWorkerScript.ts | 26 +- src/NetscriptFunctions.ts | 104 +- src/NetscriptFunctions/Darknet.ts | 740 +++++++++ src/NetscriptFunctions/Formulas.ts | 29 + src/NetscriptFunctions/Singularity.ts | 27 +- src/NetscriptFunctions/StockMarket.ts | 13 +- src/NetscriptWorker.ts | 7 +- src/Paths/CacheFilePath.ts | 17 + src/Paths/Directory.ts | 2 + src/PersonObjects/formulas/reputation.ts | 14 +- src/Prestige.ts | 17 +- src/Programs/Enums.ts | 2 + src/Programs/Program.ts | 5 +- src/Programs/Programs.ts | 31 + src/SaveObject.ts | 13 + src/ScriptEditor/NetscriptDefinitions.d.ts | 578 ++++++- src/Server/AllServers.ts | 141 +- src/Server/BaseServer.ts | 6 +- src/Server/DarknetServer.ts | 97 ++ src/Server/Server.ts | 4 +- src/Server/ServerHelpers.ts | 107 +- src/Server/data/SpecialServers.ts | 7 + src/Server/data/servers.ts | 8 - src/Sidebar/ui/SidebarRoot.tsx | 8 + src/SourceFile/SourceFiles.tsx | 1 + src/SourceFile/applySourceFile.ts | 7 +- src/StockMarket/StockMarket.ts | 6 +- src/StockMarket/ui/StockTickerHeaderText.tsx | 4 +- src/Terminal/OutputTypes.tsx | 4 +- src/Terminal/Terminal.ts | 60 +- src/Terminal/commands/backdoor.ts | 5 +- src/Terminal/commands/common/editor.ts | 6 +- src/Terminal/commands/ls.tsx | 12 +- src/Terminal/commands/run.ts | 3 + src/Terminal/commands/runProgram.ts | 3 +- src/Terminal/getTabCompletionPossibilities.ts | 13 +- src/Work/Formulas.ts | 5 +- src/engine.tsx | 17 +- src/ui/ActiveScripts/ServerAccordion.tsx | 11 +- src/ui/AutoCompleteSearchBox.tsx | 85 ++ src/ui/CharacterStats.tsx | 3 + src/ui/Enums.ts | 1 + src/ui/GameRoot.tsx | 7 +- .../InteractiveTutorialRoot.tsx | 215 ++- src/ui/React/Snackbar.tsx | 1 - src/utils/APIBreaks/3.0.0.ts | 10 + src/utils/KeyBindingUtils.ts | 2 + src/utils/MoneySourceTracker.ts | 1 + src/utils/SaveDataMigrationUtils.ts | 22 +- src/utils/TypeAssertion.ts | 20 + src/utils/Utility.ts | 6 + test/jest/Darknet/Darknet.test.ts | 751 ++++++++++ test/jest/Darknet/Labyrinth.test.ts | 308 ++++ test/jest/Migration/Migration.test.ts | 20 + test/jest/Netscript/Darknet.test.ts | 1322 +++++++++++++++++ test/jest/Netscript/Singularity.test.ts | 76 +- test/jest/Terminal/tabCompletion.test.ts | 1 + test/jest/Utilities.ts | 45 +- test/jest/__snapshots__/FullSave.test.ts.snap | 2 + 225 files changed, 15010 insertions(+), 526 deletions(-) create mode 100644 assets/Steam/achievements/icons/BN15+.svg create mode 100644 assets/Steam/achievements/icons/SF15.1.svg create mode 100644 assets/Steam/achievements/icons/darknet-backdoor.svg create mode 100644 dist/icons/achievements/BN15+.svg create mode 100644 dist/icons/achievements/SF15.1.svg create mode 100644 dist/icons/achievements/darknet-backdoor.svg create mode 100644 markdown/bitburner.cacheresult.md create mode 100644 markdown/bitburner.darknet.authenticate.md create mode 100644 markdown/bitburner.darknet.connecttosession.md create mode 100644 markdown/bitburner.darknet.getblockedram.md create mode 100644 markdown/bitburner.darknet.getdarknetinstability.md create mode 100644 markdown/bitburner.darknet.getdepth.md create mode 100644 markdown/bitburner.darknet.getserverauthdetails.md create mode 100644 markdown/bitburner.darknet.getserverrequiredcharismalevel.md create mode 100644 markdown/bitburner.darknet.getstasislinkedservers.md create mode 100644 markdown/bitburner.darknet.getstasislinklimit.md create mode 100644 markdown/bitburner.darknet.heartbleed.md create mode 100644 markdown/bitburner.darknet.induceservermigration.md create mode 100644 markdown/bitburner.darknet.isdarknetserver.md create mode 100644 markdown/bitburner.darknet.labradar.md create mode 100644 markdown/bitburner.darknet.labreport.md create mode 100644 markdown/bitburner.darknet.md create mode 100644 markdown/bitburner.darknet.memoryreallocation.md create mode 100644 markdown/bitburner.darknet.nextmutation.md create mode 100644 markdown/bitburner.darknet.opencache.md create mode 100644 markdown/bitburner.darknet.packetcapture.md create mode 100644 markdown/bitburner.darknet.phishingattack.md create mode 100644 markdown/bitburner.darknet.probe.md create mode 100644 markdown/bitburner.darknet.promotestock.md create mode 100644 markdown/bitburner.darknet.setstasislink.md create mode 100644 markdown/bitburner.darknet.unleashstormseed.md create mode 100644 markdown/bitburner.darknetformulas.getauthenticatetime.md create mode 100644 markdown/bitburner.darknetformulas.getexpectedramblockremoved.md create mode 100644 markdown/bitburner.darknetformulas.getheartbleedtime.md create mode 100644 markdown/bitburner.darknetformulas.md create mode 100644 markdown/bitburner.darknetinstability.md create mode 100644 markdown/bitburner.darknetresponsecode.md create mode 100644 markdown/bitburner.darknetresponsecodetype.md create mode 100644 markdown/bitburner.darknetresult.md create mode 100644 markdown/bitburner.darknetserverdata.backdoorinstalled.md create mode 100644 markdown/bitburner.darknetserverdata.blockedram.md create mode 100644 markdown/bitburner.darknetserverdata.cpucores.md create mode 100644 markdown/bitburner.darknetserverdata.depth.md create mode 100644 markdown/bitburner.darknetserverdata.difficulty.md create mode 100644 markdown/bitburner.darknetserverdata.hasadminrights.md create mode 100644 markdown/bitburner.darknetserverdata.hasstasislink.md create mode 100644 markdown/bitburner.darknetserverdata.hostname.md create mode 100644 markdown/bitburner.darknetserverdata.ip.md create mode 100644 markdown/bitburner.darknetserverdata.isconnectedto.md create mode 100644 markdown/bitburner.darknetserverdata.isstationary.md create mode 100644 markdown/bitburner.darknetserverdata.logtrafficinterval.md create mode 100644 markdown/bitburner.darknetserverdata.maxram.md create mode 100644 markdown/bitburner.darknetserverdata.md create mode 100644 markdown/bitburner.darknetserverdata.modelid.md create mode 100644 markdown/bitburner.darknetserverdata.passwordhintdata.md create mode 100644 markdown/bitburner.darknetserverdata.purchasedbyplayer.md create mode 100644 markdown/bitburner.darknetserverdata.ramused.md create mode 100644 markdown/bitburner.darknetserverdata.requiredcharismaskill.md create mode 100644 markdown/bitburner.darknetserverdata.staticpasswordhint.md create mode 100644 markdown/bitburner.formulas.dnet.md create mode 100644 markdown/bitburner.heartbleedoptions.md create mode 100644 markdown/bitburner.ns.dnet.md create mode 100644 markdown/bitburner.serverauthdetails.md create mode 100644 src/DarkNet/Constants.ts create mode 100644 src/DarkNet/DWRoot.tsx create mode 100644 src/DarkNet/Enums.ts create mode 100644 src/DarkNet/controllers/NetworkGenerator.ts create mode 100644 src/DarkNet/controllers/NetworkMovement.ts create mode 100644 src/DarkNet/controllers/ServerGenerator.ts create mode 100644 src/DarkNet/effects/SaveLoad.ts create mode 100644 src/DarkNet/effects/authentication.ts create mode 100644 src/DarkNet/effects/cacheFiles.ts create mode 100644 src/DarkNet/effects/effects.ts create mode 100644 src/DarkNet/effects/labyrinth.ts create mode 100644 src/DarkNet/effects/offlineServerHandling.ts create mode 100644 src/DarkNet/effects/phishing.ts create mode 100644 src/DarkNet/effects/ramblock.ts create mode 100644 src/DarkNet/effects/webstorm.ts create mode 100644 src/DarkNet/models/DarknetServerOptions.ts create mode 100644 src/DarkNet/models/DarknetState.ts create mode 100644 src/DarkNet/models/dictionaryData.ts create mode 100644 src/DarkNet/models/hintNotes.ts create mode 100644 src/DarkNet/models/packetSniffing.ts create mode 100644 src/DarkNet/ui/LabyrinthSummary.tsx create mode 100644 src/DarkNet/ui/NetworkDisplayWrapper.tsx create mode 100644 src/DarkNet/ui/PasswordPrompt.tsx create mode 100644 src/DarkNet/ui/ServerDetailsModal.tsx create mode 100644 src/DarkNet/ui/ServerIcon.ts create mode 100644 src/DarkNet/ui/ServerStatusBox.tsx create mode 100644 src/DarkNet/ui/ServerSummary.tsx create mode 100644 src/DarkNet/ui/dnetStyles.ts create mode 100644 src/DarkNet/ui/networkCanvas.ts create mode 100644 src/DarkNet/ui/uiUtilities.tsx create mode 100644 src/DarkNet/utils/darknetAuthUtils.ts create mode 100644 src/DarkNet/utils/darknetNetworkUtils.ts create mode 100644 src/DarkNet/utils/darknetServerUtils.ts create mode 100644 src/DevMenu/ui/DarknetDev.tsx create mode 100644 src/Documentation/doc/en/programming/darknet.md create mode 100644 src/NetscriptFunctions/Darknet.ts create mode 100644 src/Paths/CacheFilePath.ts create mode 100644 src/Server/DarknetServer.ts create mode 100644 src/ui/AutoCompleteSearchBox.tsx create mode 100644 src/utils/Utility.ts create mode 100644 test/jest/Darknet/Darknet.test.ts create mode 100644 test/jest/Darknet/Labyrinth.test.ts create mode 100644 test/jest/Netscript/Darknet.test.ts diff --git a/assets/Steam/achievements/icons/BN15+.svg b/assets/Steam/achievements/icons/BN15+.svg new file mode 100644 index 000000000..3850c36b9 --- /dev/null +++ b/assets/Steam/achievements/icons/BN15+.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + BN15+ + + diff --git a/assets/Steam/achievements/icons/SF15.1.svg b/assets/Steam/achievements/icons/SF15.1.svg new file mode 100644 index 000000000..f332cf377 --- /dev/null +++ b/assets/Steam/achievements/icons/SF15.1.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + SF15.1 + + diff --git a/assets/Steam/achievements/icons/darknet-backdoor.svg b/assets/Steam/achievements/icons/darknet-backdoor.svg new file mode 100644 index 000000000..d2987691d --- /dev/null +++ b/assets/Steam/achievements/icons/darknet-backdoor.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + 🔓 + + diff --git a/dist/icons/achievements/BN15+.svg b/dist/icons/achievements/BN15+.svg new file mode 100644 index 000000000..2dcf12e43 --- /dev/null +++ b/dist/icons/achievements/BN15+.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + BN15+ + + diff --git a/dist/icons/achievements/SF15.1.svg b/dist/icons/achievements/SF15.1.svg new file mode 100644 index 000000000..0f3005069 --- /dev/null +++ b/dist/icons/achievements/SF15.1.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + SF15.1 + + diff --git a/dist/icons/achievements/darknet-backdoor.svg b/dist/icons/achievements/darknet-backdoor.svg new file mode 100644 index 000000000..de97bd004 --- /dev/null +++ b/dist/icons/achievements/darknet-backdoor.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + 🔓 + + diff --git a/markdown/bitburner._valueof.md b/markdown/bitburner._valueof.md index 87a79b6f1..6b8c7d093 100644 --- a/markdown/bitburner._valueof.md +++ b/markdown/bitburner._valueof.md @@ -8,5 +8,5 @@ **Signature:** ```typescript -type _ValueOf = T[keyof T]; +export type _ValueOf = T[keyof T]; ``` diff --git a/markdown/bitburner.cacheresult.md b/markdown/bitburner.cacheresult.md new file mode 100644 index 000000000..e99d0049c --- /dev/null +++ b/markdown/bitburner.cacheresult.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [CacheResult](./bitburner.cacheresult.md) + +## CacheResult type + + +**Signature:** + +```typescript +export type CacheResult = { + success: boolean; + message: string; + karmaLoss: number; +}; +``` diff --git a/markdown/bitburner.codingcontract.attempt.md b/markdown/bitburner.codingcontract.attempt.md index ead357f7b..1f00f9d9f 100644 --- a/markdown/bitburner.codingcontract.attempt.md +++ b/markdown/bitburner.codingcontract.attempt.md @@ -74,7 +74,7 @@ string -_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to current server if not provided. diff --git a/markdown/bitburner.codingcontract.getcontract.md b/markdown/bitburner.codingcontract.getcontract.md index d6fa0fa97..55915c79a 100644 --- a/markdown/bitburner.codingcontract.getcontract.md +++ b/markdown/bitburner.codingcontract.getcontract.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the server containing the contract. Optional. Default to the current server if not provided. diff --git a/markdown/bitburner.codingcontract.getcontracttype.md b/markdown/bitburner.codingcontract.getcontracttype.md index cdfc4f63b..55410cc0d 100644 --- a/markdown/bitburner.codingcontract.getcontracttype.md +++ b/markdown/bitburner.codingcontract.getcontracttype.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to current server if not provided. diff --git a/markdown/bitburner.codingcontract.getdata.md b/markdown/bitburner.codingcontract.getdata.md index 3b975245a..0b5dc39fb 100644 --- a/markdown/bitburner.codingcontract.getdata.md +++ b/markdown/bitburner.codingcontract.getdata.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to current server if not provided. diff --git a/markdown/bitburner.codingcontract.getdescription.md b/markdown/bitburner.codingcontract.getdescription.md index 709e29d0a..05abf2a15 100644 --- a/markdown/bitburner.codingcontract.getdescription.md +++ b/markdown/bitburner.codingcontract.getdescription.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to current server if not provided. diff --git a/markdown/bitburner.codingcontract.getnumtriesremaining.md b/markdown/bitburner.codingcontract.getnumtriesremaining.md index d46acef43..22e9e5df8 100644 --- a/markdown/bitburner.codingcontract.getnumtriesremaining.md +++ b/markdown/bitburner.codingcontract.getnumtriesremaining.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the server containing the contract. Optional. Defaults to current server if not provided. diff --git a/markdown/bitburner.darknet.authenticate.md b/markdown/bitburner.darknet.authenticate.md new file mode 100644 index 000000000..3624054f0 --- /dev/null +++ b/markdown/bitburner.darknet.authenticate.md @@ -0,0 +1,94 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [authenticate](./bitburner.darknet.authenticate.md) + +## Darknet.authenticate() method + +Sends a network request to try to authenticate on a darkweb server. The target server must be directly connected to the server that the script is running on. The speed of authentication scales with the number of threads used. + +If successful, grants the script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() \*from\* the server is always allowed.) + +**Signature:** + +```typescript +authenticate(host: string, password: string, additionalMsec?: number): Promise; +``` + +## Parameters + + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +Hostname/IP of the target server (connected to the current server) to try a password. + + +
+ +password + + + + +string + + + + +Password to attempt to authenticate with. + + +
+ +additionalMsec + + + + +number + + + + +_(Optional)_ Optional. The number of additional milliseconds to add to the run time of the authentication request. Default is 0. + + +
+ +**Returns:** + +Promise<[DarknetResult](./bitburner.darknetresult.md) & { data?: any }> + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object. The resolved object may contain an optional property. The type of this property is intentionaly undocumented. You are supposed to experiment and discover the content of this property. + +## Remarks + +RAM cost: 0.6 GB + diff --git a/markdown/bitburner.darknet.connecttosession.md b/markdown/bitburner.darknet.connecttosession.md new file mode 100644 index 000000000..489db2955 --- /dev/null +++ b/markdown/bitburner.darknet.connecttosession.md @@ -0,0 +1,80 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [connectToSession](./bitburner.darknet.connecttosession.md) + +## Darknet.connectToSession() method + +Attempts to connect to a target darkweb server that you have previously authenticated on. Unlike `authenticate`, connectToSession can be used to get a session on servers at any distance. + +If successful, grants the script a session, allowing it to scp() files from that target. It also allows starting scripts with exec() on that target, if the target is directly connected to the server that the script is running on, or has a backdoor or stasis link. + +If unsuccessful, more detail may be able to be gathered by using heartbleed() to look at the resulting logs on the server. + +**Signature:** + +```typescript +connectToSession(host: string, password: string): DarknetResult; +``` + +## Parameters + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +Hostname/IP of the target server to connect to existing session + + +
+ +password + + + + +string + + + + +The server's password, to verify the session + + +
+ +**Returns:** + +[DarknetResult](./bitburner.darknetresult.md) + +A [DarknetResult](./bitburner.darknetresult.md) object + +## Remarks + +RAM cost: 0.05 GB + diff --git a/markdown/bitburner.darknet.getblockedram.md b/markdown/bitburner.darknet.getblockedram.md new file mode 100644 index 000000000..1668d73e3 --- /dev/null +++ b/markdown/bitburner.darknet.getblockedram.md @@ -0,0 +1,60 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [getBlockedRam](./bitburner.darknet.getblockedram.md) + +## Darknet.getBlockedRam() method + +Gets the amount of RAM blocked by the server owner's processes. This ram can be freed for use using memoryReallocation(). + +**Signature:** + +```typescript +getBlockedRam(host?: string): number; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +_(Optional)_ Optional. Hostname/IP of the server to check. Defaults to the running script's server. + + +
+ +**Returns:** + +number + +The amount of RAM blocked by the server owner's processes. + +## Remarks + +RAM cost: 0 GB + diff --git a/markdown/bitburner.darknet.getdarknetinstability.md b/markdown/bitburner.darknet.getdarknetinstability.md new file mode 100644 index 000000000..1e720734e --- /dev/null +++ b/markdown/bitburner.darknet.getdarknetinstability.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [getDarknetInstability](./bitburner.darknet.getdarknetinstability.md) + +## Darknet.getDarknetInstability() method + +Gets the current instability of the darknet caused by excessive backdoor-ing of servers. + +**Signature:** + +```typescript +getDarknetInstability(): DarknetInstability; +``` +**Returns:** + +[DarknetInstability](./bitburner.darknetinstability.md) + +An object containing the current instability values. + +## Remarks + +Ram cost: 0 GB + diff --git a/markdown/bitburner.darknet.getdepth.md b/markdown/bitburner.darknet.getdepth.md new file mode 100644 index 000000000..159ad2979 --- /dev/null +++ b/markdown/bitburner.darknet.getdepth.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [getDepth](./bitburner.darknet.getdepth.md) + +## Darknet.getDepth() method + +Gets the current depth of the specified server into the darknet. Servers immediately below Darkweb are depth 0, and each visual row in the UI below that increases the depth of the server. + +Returns -1 if the server is offline, not found, or not a darkweb server. + +**Signature:** + +```typescript +getDepth(host?: string): number; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +_(Optional)_ Optional. Hostname/IP of the server to check. Defaults to the running script's server. + + +
+ +**Returns:** + +number + +The current depth of the server into the darknet. + +## Remarks + +RAM cost: 0.1 GB + diff --git a/markdown/bitburner.darknet.getserverauthdetails.md b/markdown/bitburner.darknet.getserverauthdetails.md new file mode 100644 index 000000000..72ae54d58 --- /dev/null +++ b/markdown/bitburner.darknet.getserverauthdetails.md @@ -0,0 +1,60 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [getServerAuthDetails](./bitburner.darknet.getserverauthdetails.md) + +## Darknet.getServerAuthDetails() method + +Returns the server's authentication protocol details. + +**Signature:** + +```typescript +getServerAuthDetails(host?: string): ServerAuthDetails & { isOnline: boolean }; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +_(Optional)_ Hostname/IP of the server to analyze. Defaults to the running script's server if not specified. + + +
+ +**Returns:** + +[ServerAuthDetails](./bitburner.serverauthdetails.md) & { isOnline: boolean } + +An object containing the server's authentication protocol details. + +## Remarks + +RAM cost: 0.1 GB + diff --git a/markdown/bitburner.darknet.getserverrequiredcharismalevel.md b/markdown/bitburner.darknet.getserverrequiredcharismalevel.md new file mode 100644 index 000000000..ee0b6c587 --- /dev/null +++ b/markdown/bitburner.darknet.getserverrequiredcharismalevel.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [getServerRequiredCharismaLevel](./bitburner.darknet.getserverrequiredcharismalevel.md) + +## Darknet.getServerRequiredCharismaLevel() method + +Gets the required charisma level to target the server with dnet.heartbleed(). + +Insufficient charisma will also cause authentication to take much longer - or, in certain servers deep in the darknet, be impossible. + +**Signature:** + +```typescript +getServerRequiredCharismaLevel(host: string): number; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +Hostname/IP of the server to check. + + +
+ +**Returns:** + +number + +Required charisma level of the host. + +## Remarks + +RAM cost: 0.1 GB + diff --git a/markdown/bitburner.darknet.getstasislinkedservers.md b/markdown/bitburner.darknet.getstasislinkedservers.md new file mode 100644 index 000000000..49f8cb23b --- /dev/null +++ b/markdown/bitburner.darknet.getstasislinkedservers.md @@ -0,0 +1,60 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [getStasisLinkedServers](./bitburner.darknet.getstasislinkedservers.md) + +## Darknet.getStasisLinkedServers() method + +Returns the hostnames/IPs of servers that have a stasis link applied. + +**Signature:** + +```typescript +getStasisLinkedServers(returnByIP?: boolean): string[]; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +returnByIP + + + + +boolean + + + + +_(Optional)_ Optional. If true, returns IPs instead of hostnames. Defaults to false. + + +
+ +**Returns:** + +string\[\] + +Hostnames/IPs + +## Remarks + +RAM cost: 0 GB + diff --git a/markdown/bitburner.darknet.getstasislinklimit.md b/markdown/bitburner.darknet.getstasislinklimit.md new file mode 100644 index 000000000..11f88bad0 --- /dev/null +++ b/markdown/bitburner.darknet.getstasislinklimit.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [getStasisLinkLimit](./bitburner.darknet.getstasislinklimit.md) + +## Darknet.getStasisLinkLimit() method + +Returns the maximum number of stasis links that can be applied globally, based on the player's current status. Stasis link limit can be increased by finding special augmentations in the deep darknet. + +**Signature:** + +```typescript +getStasisLinkLimit(): number; +``` +**Returns:** + +number + +Maximum number of stasis links. + +## Remarks + +RAM cost: 0 GB + diff --git a/markdown/bitburner.darknet.heartbleed.md b/markdown/bitburner.darknet.heartbleed.md new file mode 100644 index 000000000..29740eece --- /dev/null +++ b/markdown/bitburner.darknet.heartbleed.md @@ -0,0 +1,80 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [heartbleed](./bitburner.darknet.heartbleed.md) + +## Darknet.heartbleed() method + +Uses an exploit to extract log data from a server by sending a malformed heartbeat request. Retrieves the most recent logs on the server. This can be used to get more feedback on authentication attempts. The retrieved logs are removed from the server, unless the "peek" flag is set to true in the provided HeartbleedOptions. + +Servers will periodically produce logs themselves, as well, which sometimes are useful, but most times are not. + +The speed of capture scales with the number of threads used. See formulas.dnet.getHeartbleedTime for more information. + +**Signature:** + +```typescript +heartbleed(host: string, options?: HeartbleedOptions): Promise; +``` + +## Parameters + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +Hostname/IP of the target server. Must be directly connected to the current server. + + +
+ +options + + + + +[HeartbleedOptions](./bitburner.heartbleedoptions.md) + + + + +_(Optional)_ Optional [HeartbleedOptions](./bitburner.heartbleedoptions.md) to modify how the exploit works. + + +
+ +**Returns:** + +Promise<[DarknetResult](./bitburner.darknetresult.md) & { logs: string\[\] }> + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object, plus the scraped logs. + +## Remarks + +RAM cost: 0.6 GB + diff --git a/markdown/bitburner.darknet.induceservermigration.md b/markdown/bitburner.darknet.induceservermigration.md new file mode 100644 index 000000000..3119dc50e --- /dev/null +++ b/markdown/bitburner.darknet.induceservermigration.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [induceServerMigration](./bitburner.darknet.induceservermigration.md) + +## Darknet.induceServerMigration() method + +Increases the chance that target connected server will move to other parts of the darknet, by overloading the connections between it and the current server. Cannot target the current server. Must be run from a darknet server. + +Effect scales with threads and charisma level. + +**Signature:** + +```typescript +induceServerMigration(host: string): Promise; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +Hostname/IP of the connected server to migrate. + + +
+ +**Returns:** + +Promise<[DarknetResult](./bitburner.darknetresult.md)> + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object. + +## Remarks + +RAM cost: 4 GB + diff --git a/markdown/bitburner.darknet.isdarknetserver.md b/markdown/bitburner.darknet.isdarknetserver.md new file mode 100644 index 000000000..d2ea91214 --- /dev/null +++ b/markdown/bitburner.darknet.isdarknetserver.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [isDarknetServer](./bitburner.darknet.isdarknetserver.md) + +## Darknet.isDarknetServer() method + +Returns whether the server is a darknet server. + +Returns false if the server does not exist or has gone offline recently. This function does not DarkscapeNavigator.exe. + +**Signature:** + +```typescript +isDarknetServer(host?: string): boolean; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +_(Optional)_ Optional. Hostname/IP for the requested server object. Defaults to the running script's server. + + +
+ +**Returns:** + +boolean + +true if the server is a darknet server, false otherwise. + +## Remarks + +RAM cost: 0.1 GB + diff --git a/markdown/bitburner.darknet.labradar.md b/markdown/bitburner.darknet.labradar.md new file mode 100644 index 000000000..28baaf7c0 --- /dev/null +++ b/markdown/bitburner.darknet.labradar.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [labradar](./bitburner.darknet.labradar.md) + +## Darknet.labradar() method + +There is more than meets the eye. + +**Signature:** + +```typescript +labradar(): Promise>; +``` +**Returns:** + +Promise<[Result](./bitburner.result.md)<any>> + +## Remarks + +RAM cost: 0 GB + diff --git a/markdown/bitburner.darknet.labreport.md b/markdown/bitburner.darknet.labreport.md new file mode 100644 index 000000000..98d0dc88d --- /dev/null +++ b/markdown/bitburner.darknet.labreport.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [labreport](./bitburner.darknet.labreport.md) + +## Darknet.labreport() method + +Not all who wander are lost. + +**Signature:** + +```typescript +labreport(): Promise>; +``` +**Returns:** + +Promise<[Result](./bitburner.result.md)<any>> + +## Remarks + +RAM cost: 0 GB + diff --git a/markdown/bitburner.darknet.md b/markdown/bitburner.darknet.md new file mode 100644 index 000000000..242bb064b --- /dev/null +++ b/markdown/bitburner.darknet.md @@ -0,0 +1,328 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) + +## Darknet interface + +Darknet API + +**Signature:** + +```typescript +export interface Darknet +``` + +## Methods + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +Method + + + + +Description + + +
+ +[authenticate(host, password, additionalMsec)](./bitburner.darknet.authenticate.md) + + + + +Sends a network request to try to authenticate on a darkweb server. The target server must be directly connected to the server that the script is running on. The speed of authentication scales with the number of threads used. + +If successful, grants the script a session, allowing it to exec() scripts on that server, or scp() files to it. (scp() \*from\* the server is always allowed.) + + +
+ +[connectToSession(host, password)](./bitburner.darknet.connecttosession.md) + + + + +Attempts to connect to a target darkweb server that you have previously authenticated on. Unlike `authenticate`, connectToSession can be used to get a session on servers at any distance. + +If successful, grants the script a session, allowing it to scp() files from that target. It also allows starting scripts with exec() on that target, if the target is directly connected to the server that the script is running on, or has a backdoor or stasis link. + +If unsuccessful, more detail may be able to be gathered by using heartbleed() to look at the resulting logs on the server. + + +
+ +[getBlockedRam(host)](./bitburner.darknet.getblockedram.md) + + + + +Gets the amount of RAM blocked by the server owner's processes. This ram can be freed for use using memoryReallocation(). + + +
+ +[getDarknetInstability()](./bitburner.darknet.getdarknetinstability.md) + + + + +Gets the current instability of the darknet caused by excessive backdoor-ing of servers. + + +
+ +[getDepth(host)](./bitburner.darknet.getdepth.md) + + + + +Gets the current depth of the specified server into the darknet. Servers immediately below Darkweb are depth 0, and each visual row in the UI below that increases the depth of the server. + +Returns -1 if the server is offline, not found, or not a darkweb server. + + +
+ +[getServerAuthDetails(host)](./bitburner.darknet.getserverauthdetails.md) + + + + +Returns the server's authentication protocol details. + + +
+ +[getServerRequiredCharismaLevel(host)](./bitburner.darknet.getserverrequiredcharismalevel.md) + + + + +Gets the required charisma level to target the server with dnet.heartbleed(). + +Insufficient charisma will also cause authentication to take much longer - or, in certain servers deep in the darknet, be impossible. + + +
+ +[getStasisLinkedServers(returnByIP)](./bitburner.darknet.getstasislinkedservers.md) + + + + +Returns the hostnames/IPs of servers that have a stasis link applied. + + +
+ +[getStasisLinkLimit()](./bitburner.darknet.getstasislinklimit.md) + + + + +Returns the maximum number of stasis links that can be applied globally, based on the player's current status. Stasis link limit can be increased by finding special augmentations in the deep darknet. + + +
+ +[heartbleed(host, options)](./bitburner.darknet.heartbleed.md) + + + + +Uses an exploit to extract log data from a server by sending a malformed heartbeat request. Retrieves the most recent logs on the server. This can be used to get more feedback on authentication attempts. The retrieved logs are removed from the server, unless the "peek" flag is set to true in the provided HeartbleedOptions. + +Servers will periodically produce logs themselves, as well, which sometimes are useful, but most times are not. + +The speed of capture scales with the number of threads used. See formulas.dnet.getHeartbleedTime for more information. + + +
+ +[induceServerMigration(host)](./bitburner.darknet.induceservermigration.md) + + + + +Increases the chance that target connected server will move to other parts of the darknet, by overloading the connections between it and the current server. Cannot target the current server. Must be run from a darknet server. + +Effect scales with threads and charisma level. + + +
+ +[isDarknetServer(host)](./bitburner.darknet.isdarknetserver.md) + + + + +Returns whether the server is a darknet server. + +Returns false if the server does not exist or has gone offline recently. This function does not DarkscapeNavigator.exe. + + +
+ +[labradar()](./bitburner.darknet.labradar.md) + + + + +There is more than meets the eye. + + +
+ +[labreport()](./bitburner.darknet.labreport.md) + + + + +Not all who wander are lost. + + +
+ +[memoryReallocation(host)](./bitburner.darknet.memoryreallocation.md) + + + + +Spends some time freeing some of the RAM currently blocked by the server owner. Must target an authenticated and directly connected server. + +The amount of ram recovered scales with charisma and the number of threads used. + + +
+ +[nextMutation()](./bitburner.darknet.nextmutation.md) + + + + +Sleep until the next mutation of the network of darknet servers (which occur frequently). Note that in the majority of cases, whatever changed out on the net (if anything) will not be nearby to, or visible from, the current server. + +Some possible mutations that can occur somewhere on the darknet each cycle: + +- Nothing changes. + +- Some servers move to other locations on the net, breaking existing connections and forming new ones. + +- Some servers go offline, which in many cases is permanent - they are effectively deleted. + +- Some servers restart, which kills all running scripts on the server. + +- New servers appear on the net (which may be previously offline servers, but cleaned and with a new password). + + +
+ +[openCache(filename, suppressToast)](./bitburner.darknet.opencache.md) + + + + +Opens a .cache file on the current server to acquire its valuable contents. + + +
+ +[packetCapture(host)](./bitburner.darknet.packetcapture.md) + + + + +Spends some time listening for unsecured network traffic on an adjacent server. If you are lucky, the server password may be somewhere in all the noise. The target server must be directly connected to the server that the script is running on. + +Using multiple threads speeds up the capture process. + + +
+ +[phishingAttack()](./bitburner.darknet.phishingattack.md) + + + + +Spends time sending out phishing emails, attempting to find some non-technical middle manager to fall for the scam. Builds charisma. Often the attempt will fail, but success can be increased with crime success rate and charisma stats. + +The amount of money lifted scales with the number of threads used, if successful. Very occasionally you can retrieve a cache file from the attempt. + +Phishing attacks can only be run from scripts on darknet servers. + + +
+ +[probe(returnByIP)](./bitburner.darknet.probe.md) + + + + +Returns a list of all darknet servers connected to the script's current server. For example, if called from a script running on `home`, it will return `["darkweb"]`. It will return an empty list if there are no darknet servers connected to the current server. + +Note that there is no guarantee about the order of servers in the returned list. + + +
+ +[promoteStock(sym)](./bitburner.darknet.promotestock.md) + + + + +Spends some time spreading propaganda about a stock to increase its volatility. This does not actually change the stock's forecasts, but a savvy investor can take advantage of the chaos. The effect scales with charisma and the number of threads used, but degrades over time if left alone. + + +
+ +[setStasisLink(shouldLink)](./bitburner.darknet.setstasislink.md) + + + + +Applies or removes a stasis link to the script's current server. This will allow you to connectToSession() or exec() to the server remotely, even if it is not directly connected to the server a script is running on. It also allows direct connection to the server via the terminal. + +Stasis links also prevent the server from going offline or moving. It does not prevent other servers from moving or going offline, though, so it does not guarantee that the stasis link server will never lose connections to other servers. + +There is a maximum of stasis links that can be applied globally, which can be seen using getStasisLinkLimit(). This limit can be increased by finding special augmentations in the deep darknet. + + +
+ +[unleashStormSeed()](./bitburner.darknet.unleashstormseed.md) + + + + +Executes STORM\_SEED.exe, if it is present on the server the script is running on. + +Warning: That exe file creates a webstorm that can cause catastrophic damage to the darknet. Run at your own risk. + + +
+ diff --git a/markdown/bitburner.darknet.memoryreallocation.md b/markdown/bitburner.darknet.memoryreallocation.md new file mode 100644 index 000000000..b5e0d5164 --- /dev/null +++ b/markdown/bitburner.darknet.memoryreallocation.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [memoryReallocation](./bitburner.darknet.memoryreallocation.md) + +## Darknet.memoryReallocation() method + +Spends some time freeing some of the RAM currently blocked by the server owner. Must target an authenticated and directly connected server. + +The amount of ram recovered scales with charisma and the number of threads used. + +**Signature:** + +```typescript +memoryReallocation(host?: string): Promise; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +_(Optional)_ Optional. Hostname/IP of the authenticated and directly connected server to free ram from. Defaults to the running script's server. + + +
+ +**Returns:** + +Promise<[DarknetResult](./bitburner.darknetresult.md)> + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object. + +## Remarks + +RAM cost: 1 GB + diff --git a/markdown/bitburner.darknet.nextmutation.md b/markdown/bitburner.darknet.nextmutation.md new file mode 100644 index 000000000..9925e87f6 --- /dev/null +++ b/markdown/bitburner.darknet.nextmutation.md @@ -0,0 +1,33 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [nextMutation](./bitburner.darknet.nextmutation.md) + +## Darknet.nextMutation() method + +Sleep until the next mutation of the network of darknet servers (which occur frequently). Note that in the majority of cases, whatever changed out on the net (if anything) will not be nearby to, or visible from, the current server. + +Some possible mutations that can occur somewhere on the darknet each cycle: + +- Nothing changes. + +- Some servers move to other locations on the net, breaking existing connections and forming new ones. + +- Some servers go offline, which in many cases is permanent - they are effectively deleted. + +- Some servers restart, which kills all running scripts on the server. + +- New servers appear on the net (which may be previously offline servers, but cleaned and with a new password). + +**Signature:** + +```typescript +nextMutation(): Promise; +``` +**Returns:** + +Promise<void> + +## Remarks + +RAM cost: 1 GB + diff --git a/markdown/bitburner.darknet.opencache.md b/markdown/bitburner.darknet.opencache.md new file mode 100644 index 000000000..0f7e659a9 --- /dev/null +++ b/markdown/bitburner.darknet.opencache.md @@ -0,0 +1,76 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [openCache](./bitburner.darknet.opencache.md) + +## Darknet.openCache() method + +Opens a .cache file on the current server to acquire its valuable contents. + +**Signature:** + +```typescript +openCache(filename: string, suppressToast?: boolean): CacheResult; +``` + +## Parameters + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +filename + + + + +string + + + + +Name of the cache file to open. + + +
+ +suppressToast + + + + +boolean + + + + +_(Optional)_ Optional. If true, suppresses the toast notification that appears when opening a cache file. Defaults to false. + + +
+ +**Returns:** + +[CacheResult](./bitburner.cacheresult.md) + +An object containing the contents of the cache, and the karma lost. + +## Remarks + +RAM cost: 2 GB + diff --git a/markdown/bitburner.darknet.packetcapture.md b/markdown/bitburner.darknet.packetcapture.md new file mode 100644 index 000000000..1dfb79075 --- /dev/null +++ b/markdown/bitburner.darknet.packetcapture.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [packetCapture](./bitburner.darknet.packetcapture.md) + +## Darknet.packetCapture() method + +Spends some time listening for unsecured network traffic on an adjacent server. If you are lucky, the server password may be somewhere in all the noise. The target server must be directly connected to the server that the script is running on. + +Using multiple threads speeds up the capture process. + +**Signature:** + +```typescript +packetCapture(host: string): Promise; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +host + + + + +string + + + + +Hostname/IP of the server to listen to. + + +
+ +**Returns:** + +Promise<[DarknetResult](./bitburner.darknetresult.md) & { data: string }> + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object, plus the captured data. + +## Remarks + +RAM cost: 6 GB + diff --git a/markdown/bitburner.darknet.phishingattack.md b/markdown/bitburner.darknet.phishingattack.md new file mode 100644 index 000000000..68a42a72b --- /dev/null +++ b/markdown/bitburner.darknet.phishingattack.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [phishingAttack](./bitburner.darknet.phishingattack.md) + +## Darknet.phishingAttack() method + +Spends time sending out phishing emails, attempting to find some non-technical middle manager to fall for the scam. Builds charisma. Often the attempt will fail, but success can be increased with crime success rate and charisma stats. + +The amount of money lifted scales with the number of threads used, if successful. Very occasionally you can retrieve a cache file from the attempt. + +Phishing attacks can only be run from scripts on darknet servers. + +**Signature:** + +```typescript +phishingAttack(): Promise; +``` +**Returns:** + +Promise<[DarknetResult](./bitburner.darknetresult.md)> + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object. + +## Remarks + +RAM cost: 2 GB + diff --git a/markdown/bitburner.darknet.probe.md b/markdown/bitburner.darknet.probe.md new file mode 100644 index 000000000..f2876881e --- /dev/null +++ b/markdown/bitburner.darknet.probe.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [probe](./bitburner.darknet.probe.md) + +## Darknet.probe() method + +Returns a list of all darknet servers connected to the script's current server. For example, if called from a script running on `home`, it will return `["darkweb"]`. It will return an empty list if there are no darknet servers connected to the current server. + +Note that there is no guarantee about the order of servers in the returned list. + +**Signature:** + +```typescript +probe(returnByIP?: boolean): string[]; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +returnByIP + + + + +boolean + + + + +_(Optional)_ Optional. Controls whether the function returns IPs instead of hostnames. Defaults to false. + + +
+ +**Returns:** + +string\[\] + +An array of strings containing the hostnames/IPs of all servers connected to the current server. + +## Remarks + +RAM cost: 0.2 GB + diff --git a/markdown/bitburner.darknet.promotestock.md b/markdown/bitburner.darknet.promotestock.md new file mode 100644 index 000000000..d3bbbd35e --- /dev/null +++ b/markdown/bitburner.darknet.promotestock.md @@ -0,0 +1,60 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [promoteStock](./bitburner.darknet.promotestock.md) + +## Darknet.promoteStock() method + +Spends some time spreading propaganda about a stock to increase its volatility. This does not actually change the stock's forecasts, but a savvy investor can take advantage of the chaos. The effect scales with charisma and the number of threads used, but degrades over time if left alone. + +**Signature:** + +```typescript +promoteStock(sym: string): Promise; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +sym + + + + +string + + + + +Stock symbol. + + +
+ +**Returns:** + +Promise<[DarknetResult](./bitburner.darknetresult.md)> + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object. + +## Remarks + +RAM cost: 2 GB + diff --git a/markdown/bitburner.darknet.setstasislink.md b/markdown/bitburner.darknet.setstasislink.md new file mode 100644 index 000000000..85399cfb8 --- /dev/null +++ b/markdown/bitburner.darknet.setstasislink.md @@ -0,0 +1,64 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [setStasisLink](./bitburner.darknet.setstasislink.md) + +## Darknet.setStasisLink() method + +Applies or removes a stasis link to the script's current server. This will allow you to connectToSession() or exec() to the server remotely, even if it is not directly connected to the server a script is running on. It also allows direct connection to the server via the terminal. + +Stasis links also prevent the server from going offline or moving. It does not prevent other servers from moving or going offline, though, so it does not guarantee that the stasis link server will never lose connections to other servers. + +There is a maximum of stasis links that can be applied globally, which can be seen using getStasisLinkLimit(). This limit can be increased by finding special augmentations in the deep darknet. + +**Signature:** + +```typescript +setStasisLink(shouldLink?: boolean): Promise; +``` + +## Parameters + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +shouldLink + + + + +boolean + + + + +_(Optional)_ true to apply a stasis link, false to remove it. Optional. Defaults to true. + + +
+ +**Returns:** + +Promise<[DarknetResult](./bitburner.darknetresult.md)> + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object. + +## Remarks + +RAM cost: 12 GB + diff --git a/markdown/bitburner.darknet.unleashstormseed.md b/markdown/bitburner.darknet.unleashstormseed.md new file mode 100644 index 000000000..274faa2c6 --- /dev/null +++ b/markdown/bitburner.darknet.unleashstormseed.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Darknet](./bitburner.darknet.md) > [unleashStormSeed](./bitburner.darknet.unleashstormseed.md) + +## Darknet.unleashStormSeed() method + +Executes STORM\_SEED.exe, if it is present on the server the script is running on. + +Warning: That exe file creates a webstorm that can cause catastrophic damage to the darknet. Run at your own risk. + +**Signature:** + +```typescript +unleashStormSeed(): DarknetResult; +``` +**Returns:** + +[DarknetResult](./bitburner.darknetresult.md) + +A promise that resolves to a [DarknetResult](./bitburner.darknetresult.md) object. + +## Remarks + +RAM cost: 0.1 GB + diff --git a/markdown/bitburner.darknetformulas.getauthenticatetime.md b/markdown/bitburner.darknetformulas.getauthenticatetime.md new file mode 100644 index 000000000..cba49b2b3 --- /dev/null +++ b/markdown/bitburner.darknetformulas.getauthenticatetime.md @@ -0,0 +1,86 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetFormulas](./bitburner.darknetformulas.md) > [getAuthenticateTime](./bitburner.darknetformulas.getauthenticatetime.md) + +## DarknetFormulas.getAuthenticateTime() method + +Gets the time it will take to authenticate a server. + +**Signature:** + +```typescript +getAuthenticateTime(darknetServerData: DarknetServerData, threads?: number, player?: Person): number; +``` + +## Parameters + + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +darknetServerData + + + + +[DarknetServerData](./bitburner.darknetserverdata.md) + + + + +The server to check authentication time on. + + +
+ +threads + + + + +number + + + + +_(Optional)_ The number of threads to use for the authentication. Optional, defaults to 1 + + +
+ +player + + + + +[Person](./bitburner.person.md) + + + + +_(Optional)_ The player object. Optional, defaults to the current player status + + +
+ +**Returns:** + +number + diff --git a/markdown/bitburner.darknetformulas.getexpectedramblockremoved.md b/markdown/bitburner.darknetformulas.getexpectedramblockremoved.md new file mode 100644 index 000000000..a5873edaf --- /dev/null +++ b/markdown/bitburner.darknetformulas.getexpectedramblockremoved.md @@ -0,0 +1,86 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetFormulas](./bitburner.darknetformulas.md) > [getExpectedRamBlockRemoved](./bitburner.darknetformulas.getexpectedramblockremoved.md) + +## DarknetFormulas.getExpectedRamBlockRemoved() method + +Gets the expected amount off ram that will be freed by a call to dnet.memoryReallocation + +**Signature:** + +```typescript +getExpectedRamBlockRemoved(darknetServerData: DarknetServerData, threads?: number, player?: Person): number; +``` + +## Parameters + + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +darknetServerData + + + + +[DarknetServerData](./bitburner.darknetserverdata.md) + + + + +The server to check ram freed on. + + +
+ +threads + + + + +number + + + + +_(Optional)_ The number of threads used in the memoryReallocation call. Optional, defaults to 1 + + +
+ +player + + + + +[Person](./bitburner.person.md) + + + + +_(Optional)_ The player object. Optional, defaults to the current player status + + +
+ +**Returns:** + +number + diff --git a/markdown/bitburner.darknetformulas.getheartbleedtime.md b/markdown/bitburner.darknetformulas.getheartbleedtime.md new file mode 100644 index 000000000..0620886f9 --- /dev/null +++ b/markdown/bitburner.darknetformulas.getheartbleedtime.md @@ -0,0 +1,86 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetFormulas](./bitburner.darknetformulas.md) > [getHeartbleedTime](./bitburner.darknetformulas.getheartbleedtime.md) + +## DarknetFormulas.getHeartbleedTime() method + +Gets the time it will take to scrape logs from a server. + +**Signature:** + +```typescript +getHeartbleedTime(darknetServerData: DarknetServerData, threads?: number, player?: Person): number; +``` + +## Parameters + + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +darknetServerData + + + + +[DarknetServerData](./bitburner.darknetserverdata.md) + + + + +The server to check heartbleed log scraping time on. + + +
+ +threads + + + + +number + + + + +_(Optional)_ The number of threads to use for the authentication. Optional, defaults to 1 + + +
+ +player + + + + +[Person](./bitburner.person.md) + + + + +_(Optional)_ The player object. Optional, defaults to the current player status + + +
+ +**Returns:** + +number + diff --git a/markdown/bitburner.darknetformulas.md b/markdown/bitburner.darknetformulas.md new file mode 100644 index 000000000..6ba0bebd6 --- /dev/null +++ b/markdown/bitburner.darknetformulas.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetFormulas](./bitburner.darknetformulas.md) + +## DarknetFormulas interface + +Darknet formulas + +**Signature:** + +```typescript +interface DarknetFormulas +``` + +## Methods + + + + + +
+ +Method + + + + +Description + + +
+ +[getAuthenticateTime(darknetServerData, threads, player)](./bitburner.darknetformulas.getauthenticatetime.md) + + + + +Gets the time it will take to authenticate a server. + + +
+ +[getExpectedRamBlockRemoved(darknetServerData, threads, player)](./bitburner.darknetformulas.getexpectedramblockremoved.md) + + + + +Gets the expected amount off ram that will be freed by a call to dnet.memoryReallocation + + +
+ +[getHeartbleedTime(darknetServerData, threads, player)](./bitburner.darknetformulas.getheartbleedtime.md) + + + + +Gets the time it will take to scrape logs from a server. + + +
+ diff --git a/markdown/bitburner.darknetinstability.md b/markdown/bitburner.darknetinstability.md new file mode 100644 index 000000000..cbe9f43c0 --- /dev/null +++ b/markdown/bitburner.darknetinstability.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetInstability](./bitburner.darknetinstability.md) + +## DarknetInstability type + +Instability of the darknet caused by excessive backdoor-ing of servers. + +**Signature:** + +```typescript +type DarknetInstability = { + authenticationDurationMultiplier: number; + authenticationTimeoutChance: number; +}; +``` diff --git a/markdown/bitburner.darknetresponsecode.md b/markdown/bitburner.darknetresponsecode.md new file mode 100644 index 000000000..22216ab2f --- /dev/null +++ b/markdown/bitburner.darknetresponsecode.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetResponseCode](./bitburner.darknetresponsecode.md) + +## DarknetResponseCode type + + +**Signature:** + +```typescript +type DarknetResponseCode = _ValueOf; +``` +**References:** [\_ValueOf](./bitburner._valueof.md), [DarknetResponseCodeType](./bitburner.darknetresponsecodetype.md) + diff --git a/markdown/bitburner.darknetresponsecodetype.md b/markdown/bitburner.darknetresponsecodetype.md new file mode 100644 index 000000000..537aa5c69 --- /dev/null +++ b/markdown/bitburner.darknetresponsecodetype.md @@ -0,0 +1,35 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetResponseCodeType](./bitburner.darknetresponsecodetype.md) + +## DarknetResponseCodeType type + +Errors: + +- DirectConnectionRequired: The target server is not directly connected to the current server. This may be caused by a user error (specifying the wrong neighbor host's hostname) or a network change (the target server was moved). + +- AuthFailure: Authentication failed. The password is incorrect. + +- NotFound: The API requires a specific resource (e.g., an exe file), but it does not exist on the server. + +- RequestTimeOut: The request failed (though the password may or may not have been correct). Caused by network instability. + +- ServiceUnavailable: The server is offline. + +**Signature:** + +```typescript +type DarknetResponseCodeType = { + Success: 200; + DirectConnectionRequired: 351; + AuthFailure: 401; + Forbidden: 403; + NotFound: 404; + RequestTimeOut: 408; + NotEnoughCharisma: 451; + StasisLinkLimitReached: 453; + NoBlockRAM: 454; + PhishingFailed: 455; + ServiceUnavailable: 503; +}; +``` diff --git a/markdown/bitburner.darknetresult.md b/markdown/bitburner.darknetresult.md new file mode 100644 index 000000000..d290e5921 --- /dev/null +++ b/markdown/bitburner.darknetresult.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetResult](./bitburner.darknetresult.md) + +## DarknetResult type + + +**Signature:** + +```typescript +export type DarknetResult = { success: boolean; code: DarknetResponseCode; message: string }; +``` +**References:** [DarknetResponseCode](./bitburner.darknetresponsecode.md) + diff --git a/markdown/bitburner.darknetserverdata.backdoorinstalled.md b/markdown/bitburner.darknetserverdata.backdoorinstalled.md new file mode 100644 index 000000000..abe916612 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.backdoorinstalled.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [backdoorInstalled](./bitburner.darknetserverdata.backdoorinstalled.md) + +## DarknetServerData.backdoorInstalled property + +Flag indicating whether this server has a backdoor installed by the player + +**Signature:** + +```typescript +backdoorInstalled: boolean; +``` diff --git a/markdown/bitburner.darknetserverdata.blockedram.md b/markdown/bitburner.darknetserverdata.blockedram.md new file mode 100644 index 000000000..efbd5e1c9 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.blockedram.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [blockedRam](./bitburner.darknetserverdata.blockedram.md) + +## DarknetServerData.blockedRam property + +The amount of ram blocked by the server owner + +**Signature:** + +```typescript +blockedRam: number; +``` diff --git a/markdown/bitburner.darknetserverdata.cpucores.md b/markdown/bitburner.darknetserverdata.cpucores.md new file mode 100644 index 000000000..6230e1efb --- /dev/null +++ b/markdown/bitburner.darknetserverdata.cpucores.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [cpuCores](./bitburner.darknetserverdata.cpucores.md) + +## DarknetServerData.cpuCores property + +Number of CPU cores + +**Signature:** + +```typescript +cpuCores: number; +``` diff --git a/markdown/bitburner.darknetserverdata.depth.md b/markdown/bitburner.darknetserverdata.depth.md new file mode 100644 index 000000000..e1efcbce2 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.depth.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [depth](./bitburner.darknetserverdata.depth.md) + +## DarknetServerData.depth property + +The depth of the server in the net + +**Signature:** + +```typescript +depth: number; +``` diff --git a/markdown/bitburner.darknetserverdata.difficulty.md b/markdown/bitburner.darknetserverdata.difficulty.md new file mode 100644 index 000000000..17afedf73 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.difficulty.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [difficulty](./bitburner.darknetserverdata.difficulty.md) + +## DarknetServerData.difficulty property + +The difficulty rating of the server, associated with its original depth in the net + +**Signature:** + +```typescript +difficulty: number; +``` diff --git a/markdown/bitburner.darknetserverdata.hasadminrights.md b/markdown/bitburner.darknetserverdata.hasadminrights.md new file mode 100644 index 000000000..04d1fc302 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.hasadminrights.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [hasAdminRights](./bitburner.darknetserverdata.hasadminrights.md) + +## DarknetServerData.hasAdminRights property + +Flag indicating whether the player has admin/root access to this server + +**Signature:** + +```typescript +hasAdminRights: boolean; +``` diff --git a/markdown/bitburner.darknetserverdata.hasstasislink.md b/markdown/bitburner.darknetserverdata.hasstasislink.md new file mode 100644 index 000000000..6202fcc59 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.hasstasislink.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [hasStasisLink](./bitburner.darknetserverdata.hasstasislink.md) + +## DarknetServerData.hasStasisLink property + +If the server has a stasis link applied + +**Signature:** + +```typescript +hasStasisLink: boolean; +``` diff --git a/markdown/bitburner.darknetserverdata.hostname.md b/markdown/bitburner.darknetserverdata.hostname.md new file mode 100644 index 000000000..c1dbc0b88 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.hostname.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [hostname](./bitburner.darknetserverdata.hostname.md) + +## DarknetServerData.hostname property + +Hostname. Must be unique + +**Signature:** + +```typescript +hostname: string; +``` diff --git a/markdown/bitburner.darknetserverdata.ip.md b/markdown/bitburner.darknetserverdata.ip.md new file mode 100644 index 000000000..8e03b044a --- /dev/null +++ b/markdown/bitburner.darknetserverdata.ip.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [ip](./bitburner.darknetserverdata.ip.md) + +## DarknetServerData.ip property + +IP Address. Must be unique + +**Signature:** + +```typescript +ip: string; +``` diff --git a/markdown/bitburner.darknetserverdata.isconnectedto.md b/markdown/bitburner.darknetserverdata.isconnectedto.md new file mode 100644 index 000000000..c62a4cac6 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.isconnectedto.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [isConnectedTo](./bitburner.darknetserverdata.isconnectedto.md) + +## DarknetServerData.isConnectedTo property + +Flag indicating whether the player's terminal is currently connected to this server + +**Signature:** + +```typescript +isConnectedTo: boolean; +``` diff --git a/markdown/bitburner.darknetserverdata.isstationary.md b/markdown/bitburner.darknetserverdata.isstationary.md new file mode 100644 index 000000000..674b3c046 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.isstationary.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [isStationary](./bitburner.darknetserverdata.isstationary.md) + +## DarknetServerData.isStationary property + +If this darknet server cannot be moved. True for fixed/story servers. + +**Signature:** + +```typescript +isStationary: boolean; +``` diff --git a/markdown/bitburner.darknetserverdata.logtrafficinterval.md b/markdown/bitburner.darknetserverdata.logtrafficinterval.md new file mode 100644 index 000000000..de431e362 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.logtrafficinterval.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [logTrafficInterval](./bitburner.darknetserverdata.logtrafficinterval.md) + +## DarknetServerData.logTrafficInterval property + +The interval at which the server periodically adds to its logs, in seconds. + +**Signature:** + +```typescript +logTrafficInterval: number; +``` diff --git a/markdown/bitburner.darknetserverdata.maxram.md b/markdown/bitburner.darknetserverdata.maxram.md new file mode 100644 index 000000000..61fbb23ef --- /dev/null +++ b/markdown/bitburner.darknetserverdata.maxram.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [maxRam](./bitburner.darknetserverdata.maxram.md) + +## DarknetServerData.maxRam property + +Max RAM (GB) of this server + +**Signature:** + +```typescript +maxRam: number; +``` diff --git a/markdown/bitburner.darknetserverdata.md b/markdown/bitburner.darknetserverdata.md new file mode 100644 index 000000000..98ef9ce89 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.md @@ -0,0 +1,400 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) + +## DarknetServerData interface + +Darknet server data. + +**Signature:** + +```typescript +export interface DarknetServerData +``` + +## Properties + + + + + + + + + + + + + + + + + + + + + +
+ +Property + + + + +Modifiers + + + + +Type + + + + +Description + + +
+ +[backdoorInstalled](./bitburner.darknetserverdata.backdoorinstalled.md) + + + + + + + +boolean + + + + +Flag indicating whether this server has a backdoor installed by the player + + +
+ +[blockedRam](./bitburner.darknetserverdata.blockedram.md) + + + + + + + +number + + + + +The amount of ram blocked by the server owner + + +
+ +[cpuCores](./bitburner.darknetserverdata.cpucores.md) + + + + + + + +number + + + + +Number of CPU cores + + +
+ +[depth](./bitburner.darknetserverdata.depth.md) + + + + + + + +number + + + + +The depth of the server in the net + + +
+ +[difficulty](./bitburner.darknetserverdata.difficulty.md) + + + + + + + +number + + + + +The difficulty rating of the server, associated with its original depth in the net + + +
+ +[hasAdminRights](./bitburner.darknetserverdata.hasadminrights.md) + + + + + + + +boolean + + + + +Flag indicating whether the player has admin/root access to this server + + +
+ +[hasStasisLink](./bitburner.darknetserverdata.hasstasislink.md) + + + + + + + +boolean + + + + +If the server has a stasis link applied + + +
+ +[hostname](./bitburner.darknetserverdata.hostname.md) + + + + + + + +string + + + + +Hostname. Must be unique + + +
+ +[ip](./bitburner.darknetserverdata.ip.md) + + + + + + + +string + + + + +IP Address. Must be unique + + +
+ +[isConnectedTo](./bitburner.darknetserverdata.isconnectedto.md) + + + + + + + +boolean + + + + +Flag indicating whether the player's terminal is currently connected to this server + + +
+ +[isStationary](./bitburner.darknetserverdata.isstationary.md) + + + + + + + +boolean + + + + +If this darknet server cannot be moved. True for fixed/story servers. + + +
+ +[logTrafficInterval](./bitburner.darknetserverdata.logtrafficinterval.md) + + + + + + + +number + + + + +The interval at which the server periodically adds to its logs, in seconds. + + +
+ +[maxRam](./bitburner.darknetserverdata.maxram.md) + + + + + + + +number + + + + +Max RAM (GB) of this server + + +
+ +[modelId](./bitburner.darknetserverdata.modelid.md) + + + + + + + +string + + + + +The model of the server. Similar models have similar vulnerabilities. The model list is intentionally undocumented. You are supposed to experiment and discover the models. + + +
+ +[passwordHintData](./bitburner.darknetserverdata.passwordhintdata.md) + + + + + + + +string + + + + +Data associated with the password hint + + +
+ +[purchasedByPlayer](./bitburner.darknetserverdata.purchasedbyplayer.md) + + + + + + + +boolean + + + + +Whether this server was purchased by the player. Always false for darknet servers + + +
+ +[ramUsed](./bitburner.darknetserverdata.ramused.md) + + + + + + + +number + + + + +Used RAM (GB). i.e. unavailable RAM + + +
+ +[requiredCharismaSkill](./bitburner.darknetserverdata.requiredcharismaskill.md) + + + + + + + +number + + + + +The charisma skill required to heartbleed the server + + +
+ +[staticPasswordHint](./bitburner.darknetserverdata.staticpasswordhint.md) + + + + + + + +string + + + + +The generic password prompt for the server + + +
+ diff --git a/markdown/bitburner.darknetserverdata.modelid.md b/markdown/bitburner.darknetserverdata.modelid.md new file mode 100644 index 000000000..5b07510ef --- /dev/null +++ b/markdown/bitburner.darknetserverdata.modelid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [modelId](./bitburner.darknetserverdata.modelid.md) + +## DarknetServerData.modelId property + +The model of the server. Similar models have similar vulnerabilities. The model list is intentionally undocumented. You are supposed to experiment and discover the models. + +**Signature:** + +```typescript +modelId: string; +``` diff --git a/markdown/bitburner.darknetserverdata.passwordhintdata.md b/markdown/bitburner.darknetserverdata.passwordhintdata.md new file mode 100644 index 000000000..a664e3a34 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.passwordhintdata.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [passwordHintData](./bitburner.darknetserverdata.passwordhintdata.md) + +## DarknetServerData.passwordHintData property + +Data associated with the password hint + +**Signature:** + +```typescript +passwordHintData: string; +``` diff --git a/markdown/bitburner.darknetserverdata.purchasedbyplayer.md b/markdown/bitburner.darknetserverdata.purchasedbyplayer.md new file mode 100644 index 000000000..5b4ef1d92 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.purchasedbyplayer.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [purchasedByPlayer](./bitburner.darknetserverdata.purchasedbyplayer.md) + +## DarknetServerData.purchasedByPlayer property + +Whether this server was purchased by the player. Always false for darknet servers + +**Signature:** + +```typescript +purchasedByPlayer: boolean; +``` diff --git a/markdown/bitburner.darknetserverdata.ramused.md b/markdown/bitburner.darknetserverdata.ramused.md new file mode 100644 index 000000000..b364bb7a9 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.ramused.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [ramUsed](./bitburner.darknetserverdata.ramused.md) + +## DarknetServerData.ramUsed property + +Used RAM (GB). i.e. unavailable RAM + +**Signature:** + +```typescript +ramUsed: number; +``` diff --git a/markdown/bitburner.darknetserverdata.requiredcharismaskill.md b/markdown/bitburner.darknetserverdata.requiredcharismaskill.md new file mode 100644 index 000000000..a239b82bc --- /dev/null +++ b/markdown/bitburner.darknetserverdata.requiredcharismaskill.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [requiredCharismaSkill](./bitburner.darknetserverdata.requiredcharismaskill.md) + +## DarknetServerData.requiredCharismaSkill property + +The charisma skill required to heartbleed the server + +**Signature:** + +```typescript +requiredCharismaSkill: number; +``` diff --git a/markdown/bitburner.darknetserverdata.staticpasswordhint.md b/markdown/bitburner.darknetserverdata.staticpasswordhint.md new file mode 100644 index 000000000..73f5cb1d0 --- /dev/null +++ b/markdown/bitburner.darknetserverdata.staticpasswordhint.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [DarknetServerData](./bitburner.darknetserverdata.md) > [staticPasswordHint](./bitburner.darknetserverdata.staticpasswordhint.md) + +## DarknetServerData.staticPasswordHint property + +The generic password prompt for the server + +**Signature:** + +```typescript +staticPasswordHint: string; +``` diff --git a/markdown/bitburner.formulas.dnet.md b/markdown/bitburner.formulas.dnet.md new file mode 100644 index 000000000..8b0535773 --- /dev/null +++ b/markdown/bitburner.formulas.dnet.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Formulas](./bitburner.formulas.md) > [dnet](./bitburner.formulas.dnet.md) + +## Formulas.dnet property + +Darknet formulas + +**Signature:** + +```typescript +dnet: DarknetFormulas; +``` diff --git a/markdown/bitburner.formulas.md b/markdown/bitburner.formulas.md index 26248a902..d5b6219c1 100644 --- a/markdown/bitburner.formulas.md +++ b/markdown/bitburner.formulas.md @@ -57,6 +57,25 @@ Description Bladeburner formulas + + + +[dnet](./bitburner.formulas.dnet.md) + + + + + + + +[DarknetFormulas](./bitburner.darknetformulas.md) + + + + +Darknet formulas + + diff --git a/markdown/bitburner.heartbleedoptions.md b/markdown/bitburner.heartbleedoptions.md new file mode 100644 index 000000000..f94337ded --- /dev/null +++ b/markdown/bitburner.heartbleedoptions.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [HeartbleedOptions](./bitburner.heartbleedoptions.md) + +## HeartbleedOptions type + +Options to change the behavior of [heartbleed](./bitburner.darknet.heartbleed.md) API. + +**Signature:** + +```typescript +type HeartbleedOptions = { + peek?: boolean; + logsToCapture?: number; + additionalMsec?: number; +}; +``` diff --git a/markdown/bitburner.locationnameenumtype.md b/markdown/bitburner.locationnameenumtype.md index b87e6fe43..e4122e9cf 100644 --- a/markdown/bitburner.locationnameenumtype.md +++ b/markdown/bitburner.locationnameenumtype.md @@ -28,6 +28,7 @@ type LocationNameEnumType = { ChongqingKuaiGongInternational: "KuaiGong International"; ChongqingSolarisSpaceSystems: "Solaris Space Systems"; ChongqingChurchOfTheMachineGod: "Church of the Machine God"; + ChongqingShadowedWalkway: "Shadowed Walkway"; Sector12AlphaEnterprises: "Alpha Enterprises"; Sector12BladeIndustries: "Blade Industries"; diff --git a/markdown/bitburner.md b/markdown/bitburner.md index 4fc8fff36..4664ed47e 100644 --- a/markdown/bitburner.md +++ b/markdown/bitburner.md @@ -346,6 +346,39 @@ Data representing the internal values of a crime. Crime + + + +[Darknet](./bitburner.darknet.md) + + + + +Darknet API + + + + + +[DarknetFormulas](./bitburner.darknetformulas.md) + + + + +Darknet formulas + + + + + +[DarknetServerData](./bitburner.darknetserverdata.md) + + + + +Darknet server data. + + @@ -1558,6 +1591,16 @@ Operation names of Bladeburner Skill names type of Bladeburner + + + +[CacheResult](./bitburner.cacheresult.md) + + + + + + @@ -1760,6 +1803,58 @@ Names of all companies + + + +[DarknetInstability](./bitburner.darknetinstability.md) + + + + +Instability of the darknet caused by excessive backdoor-ing of servers. + + + + + +[DarknetResponseCode](./bitburner.darknetresponsecode.md) + + + + + + + + + +[DarknetResponseCodeType](./bitburner.darknetresponsecodetype.md) + + + + +Errors: + +- DirectConnectionRequired: The target server is not directly connected to the current server. This may be caused by a user error (specifying the wrong neighbor host's hostname) or a network change (the target server was moved). + +- AuthFailure: Authentication failed. The password is incorrect. + +- NotFound: The API requires a specific resource (e.g., an exe file), but it does not exist on the server. + +- RequestTimeOut: The request failed (though the password may or may not have been correct). Caused by network instability. + +- ServiceUnavailable: The server is offline. + + + + + +[DarknetResult](./bitburner.darknetresult.md) + + + + + + @@ -1904,6 +1999,17 @@ Locations of gym + + + +[HeartbleedOptions](./bitburner.heartbleedoptions.md) + + + + +Options to change the behavior of [heartbleed](./bitburner.darknet.heartbleed.md) API. + + @@ -2059,6 +2165,17 @@ Use React.createElement to make the ReactElement type, see [creating an element + + + +[ServerAuthDetails](./bitburner.serverauthdetails.md) + + + + +Details about a server's authentication schema + + diff --git a/markdown/bitburner.ns.dnet.md b/markdown/bitburner.ns.dnet.md new file mode 100644 index 000000000..f7e94af49 --- /dev/null +++ b/markdown/bitburner.ns.dnet.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [NS](./bitburner.ns.md) > [dnet](./bitburner.ns.dnet.md) + +## NS.dnet property + +Namespace for darknet functions. Contains spoilers. + +**Signature:** + +```typescript +readonly dnet: Darknet; +``` + +## Remarks + +RAM cost: 0 GB + diff --git a/markdown/bitburner.ns.exec.md b/markdown/bitburner.ns.exec.md index b17ceec54..a37a79b48 100644 --- a/markdown/bitburner.ns.exec.md +++ b/markdown/bitburner.ns.exec.md @@ -106,7 +106,7 @@ Returns the PID of a successfully started script, and 0 otherwise. RAM cost: 1.3 GB -Run a script as a separate process on a specified server. This is similar to the function [run](./bitburner.ns.run.md) except that it can be used to run a script that already exists on any server, instead of just the current server. +Run a script as a separate process on a specified server. This is similar to the function [run](./bitburner.ns.run.md), except that it can be used to run a script that already exists on any server, instead of just the current server. If the script was successfully started, then this function returns the PID of that script. Otherwise, it returns 0. diff --git a/markdown/bitburner.ns.fileexists.md b/markdown/bitburner.ns.fileexists.md index b9e463b1b..994c4979e 100644 --- a/markdown/bitburner.ns.fileexists.md +++ b/markdown/bitburner.ns.fileexists.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of target server. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of target server. Optional, defaults to the server the script is running on. diff --git a/markdown/bitburner.ns.getgrowtime.md b/markdown/bitburner.ns.getgrowtime.md index 6a2ab1ef3..140157605 100644 --- a/markdown/bitburner.ns.getgrowtime.md +++ b/markdown/bitburner.ns.getgrowtime.md @@ -9,7 +9,7 @@ Get the execution time of a grow() call. **Signature:** ```typescript -getGrowTime(host?: string): number; +getGrowTime(host: string): number; ``` ## Parameters @@ -42,7 +42,7 @@ string -_(Optional)_ Hostname/IP of target server. Optional. Defaults to the server the calling script is running on. +Hostname/IP of target server. diff --git a/markdown/bitburner.ns.gethacktime.md b/markdown/bitburner.ns.gethacktime.md index 9f64110ee..b28eae569 100644 --- a/markdown/bitburner.ns.gethacktime.md +++ b/markdown/bitburner.ns.gethacktime.md @@ -9,7 +9,7 @@ Get the execution time of a hack() call. **Signature:** ```typescript -getHackTime(host?: string): number; +getHackTime(host: string): number; ``` ## Parameters @@ -42,7 +42,7 @@ string -_(Optional)_ Hostname/IP of target server. Optional. Defaults to the server the calling script is running on. +Hostname/IP of target server. diff --git a/markdown/bitburner.ns.getrunningscript.md b/markdown/bitburner.ns.getrunningscript.md index c20826988..4937ed7d7 100644 --- a/markdown/bitburner.ns.getrunningscript.md +++ b/markdown/bitburner.ns.getrunningscript.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of target server. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of target server. Optional, defaults to the server the calling script is running on. diff --git a/markdown/bitburner.ns.getscriptlogs.md b/markdown/bitburner.ns.getscriptlogs.md index 3770c6a43..81824ff12 100644 --- a/markdown/bitburner.ns.getscriptlogs.md +++ b/markdown/bitburner.ns.getscriptlogs.md @@ -58,7 +58,7 @@ string -_(Optional)_ Optional. Hostname/IP of the server that the script is on. Defaults to the server the calling script is running on. +_(Optional)_ Optional. Hostname/IP of the server that the script is on. diff --git a/markdown/bitburner.ns.getserver.md b/markdown/bitburner.ns.getserver.md index 7dbc2a8f3..4b1fd08a2 100644 --- a/markdown/bitburner.ns.getserver.md +++ b/markdown/bitburner.ns.getserver.md @@ -4,12 +4,14 @@ ## NS.getServer() method -Returns a server object for the given server. +Returns data of a server. + +If the server is a darknet server and has recently gone offline, it will return a dummy server object with `isOnline: false`. **Signature:** ```typescript -getServer(host?: string): Server; +getServer(host?: string): Server | (DarknetServerData & { isOnline: boolean }); ``` ## Parameters @@ -42,7 +44,7 @@ string -_(Optional)_ Optional. Hostname/IP for the requested server object. Defaults to the server the calling script is running on. +_(Optional)_ Optional. Hostname/IP of the server. Defaults to the hostname of the running script's server. @@ -50,9 +52,9 @@ _(Optional)_ Optional. Hostname/IP for the requested server object. Defaults to **Returns:** -[Server](./bitburner.server.md) +[Server](./bitburner.server.md) \| ([DarknetServerData](./bitburner.darknetserverdata.md) & { isOnline: boolean }) -The requested server object. +Data of the server. ## Remarks diff --git a/markdown/bitburner.ns.getweakentime.md b/markdown/bitburner.ns.getweakentime.md index 1399992d6..9f5a09b81 100644 --- a/markdown/bitburner.ns.getweakentime.md +++ b/markdown/bitburner.ns.getweakentime.md @@ -9,7 +9,7 @@ Get the execution time of a weaken() call. **Signature:** ```typescript -getWeakenTime(host?: string): number; +getWeakenTime(host: string): number; ``` ## Parameters @@ -42,7 +42,7 @@ string -_(Optional)_ Hostname/IP of target server. Optional. Defaults to the server the calling script is running on. +Hostname/IP of target server. diff --git a/markdown/bitburner.ns.growthanalyzesecurity.md b/markdown/bitburner.ns.growthanalyzesecurity.md index 402030bc5..b9a4e795e 100644 --- a/markdown/bitburner.ns.growthanalyzesecurity.md +++ b/markdown/bitburner.ns.growthanalyzesecurity.md @@ -58,7 +58,7 @@ string -_(Optional)_ Optional. Hostname/IP of the target server. If specified, the value of the "threads" parameter is limited to the number of threads needed to reach the specified server's maximum money. +_(Optional)_ Optional. Hostname/IP of the target server. If provided, security increase is limited by the number of threads needed to reach maximum money. diff --git a/markdown/bitburner.ns.hackanalyzesecurity.md b/markdown/bitburner.ns.hackanalyzesecurity.md index df295e0af..e520be0fd 100644 --- a/markdown/bitburner.ns.hackanalyzesecurity.md +++ b/markdown/bitburner.ns.hackanalyzesecurity.md @@ -4,7 +4,7 @@ ## NS.hackAnalyzeSecurity() method -Get the security increase for a number of hack threads. +Get the security increase for a number of threads. **Signature:** @@ -58,7 +58,7 @@ string -_(Optional)_ Optional. Hostname/IP of the target server. If specified, the value of the "threads" parameter is limited to the number of threads needed to hack the specified server's maximum amount of money. +_(Optional)_ Hostname/IP of the target server. The number of threads is limited to the number needed to hack the server's maximum amount of money. diff --git a/markdown/bitburner.ns.isrunning.md b/markdown/bitburner.ns.isrunning.md index f615eb0a3..4e6c69140 100644 --- a/markdown/bitburner.ns.isrunning.md +++ b/markdown/bitburner.ns.isrunning.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of target server. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of target server. Optional, defaults to the server the calling script is running on. diff --git a/markdown/bitburner.ns.kill_1.md b/markdown/bitburner.ns.kill_1.md index 224eba5f4..1a73f81f6 100644 --- a/markdown/bitburner.ns.kill_1.md +++ b/markdown/bitburner.ns.kill_1.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP where the script to kill is running. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP where the script to kill is running. Defaults to the current server. diff --git a/markdown/bitburner.ns.killall.md b/markdown/bitburner.ns.killall.md index 1800feb34..dd8a55e46 100644 --- a/markdown/bitburner.ns.killall.md +++ b/markdown/bitburner.ns.killall.md @@ -42,7 +42,7 @@ string -_(Optional)_ Hostname/IP of the server on which to kill all scripts. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the server on which to kill all scripts. @@ -58,7 +58,7 @@ boolean -_(Optional)_ Skips the script that calls this function. Optional. Defaults to false. +_(Optional)_ Skips the script that calls this function diff --git a/markdown/bitburner.ns.md b/markdown/bitburner.ns.md index 00b49056d..43c291e42 100644 --- a/markdown/bitburner.ns.md +++ b/markdown/bitburner.ns.md @@ -154,6 +154,27 @@ Namespace for [coding contract](./bitburner.codingcontract.md) functions. Namespace for [corporation](./bitburner.corporation.md) functions. Contains spoilers. + + + +[dnet](./bitburner.ns.dnet.md) + + + + +`readonly` + + + + +[Darknet](./bitburner.darknet.md) + + + + +Namespace for darknet functions. Contains spoilers. + + @@ -904,7 +925,9 @@ Get the ram cost of a script. -Returns a server object for the given server. +Returns data of a server. + +If the server is a darknet server and has recently gone offline, it will return a dummy server object with `isOnline: false`. @@ -1135,7 +1158,7 @@ Get the chance of successfully hacking a server. -Get the security increase for a number of hack threads. +Get the security increase for a number of threads. @@ -1421,7 +1444,7 @@ Start another script on the current server. -Get the list of hostnames or IP addresses connected to a server. +Get the list of hostnames or IP addresses connected to a server. This function does not return darknet servers (e.g., darkweb). Use [probe](./bitburner.darknet.probe.md) if you want to list darknet servers. diff --git a/markdown/bitburner.ns.ps.md b/markdown/bitburner.ns.ps.md index 86ff0d29c..9bb0be296 100644 --- a/markdown/bitburner.ns.ps.md +++ b/markdown/bitburner.ns.ps.md @@ -42,7 +42,7 @@ string -_(Optional)_ Hostname/IP of the target server. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the target server. If not specified, it will be the current server’s IP by default. diff --git a/markdown/bitburner.ns.rm.md b/markdown/bitburner.ns.rm.md index fd7a1ff5b..724fc1be0 100644 --- a/markdown/bitburner.ns.rm.md +++ b/markdown/bitburner.ns.rm.md @@ -58,7 +58,7 @@ string -_(Optional)_ Hostname/IP of the server on which to delete the file. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Hostname/IP of the server on which to delete the file. Optional. Defaults to current server. diff --git a/markdown/bitburner.ns.scan.md b/markdown/bitburner.ns.scan.md index 83ec364cb..19f3a43b8 100644 --- a/markdown/bitburner.ns.scan.md +++ b/markdown/bitburner.ns.scan.md @@ -4,7 +4,7 @@ ## NS.scan() method -Get the list of hostnames or IP addresses connected to a server. +Get the list of hostnames or IP addresses connected to a server. This function does not return darknet servers (e.g., darkweb). Use [probe](./bitburner.darknet.probe.md) if you want to list darknet servers. **Signature:** @@ -42,7 +42,7 @@ string \| null -_(Optional)_ Optional. Hostname/IP of the server to scan. Defaults to the server the calling script is running on. +_(Optional)_ Optional. Hostname/IP of the server to scan, default to current server. diff --git a/markdown/bitburner.ns.scp.md b/markdown/bitburner.ns.scp.md index 2606d1e4c..d625135d1 100644 --- a/markdown/bitburner.ns.scp.md +++ b/markdown/bitburner.ns.scp.md @@ -112,4 +112,5 @@ const server = ns.args[0]; const files = ["hack.js", "weaken.js", "grow.js"]; ns.scp(files, server, "home"); ``` +For password-protected servers (such as darknet servers), a session must be established with the destination server before using this function. diff --git a/markdown/bitburner.ns.spawn.md b/markdown/bitburner.ns.spawn.md index 8db8e5cb3..1907a111f 100644 --- a/markdown/bitburner.ns.spawn.md +++ b/markdown/bitburner.ns.spawn.md @@ -96,6 +96,8 @@ Because this function immediately terminates the script, it does not have a retu Running this function with 0 or fewer threads will cause a runtime error. +For password-protected servers (such as darknet servers), a session must be established with the destination server before using this function. + ## Example diff --git a/markdown/bitburner.ns.wget.md b/markdown/bitburner.ns.wget.md index 7e870c968..850fb789b 100644 --- a/markdown/bitburner.ns.wget.md +++ b/markdown/bitburner.ns.wget.md @@ -74,7 +74,7 @@ string -_(Optional)_ Hostname/IP of server for target file. Optional. Defaults to the server the calling script is running on. +_(Optional)_ Optional hostname/ip of server for target file. diff --git a/markdown/bitburner.nsenums.md b/markdown/bitburner.nsenums.md index 460e94e58..6730bb759 100644 --- a/markdown/bitburner.nsenums.md +++ b/markdown/bitburner.nsenums.md @@ -26,7 +26,8 @@ type NSEnums = { BladeburnerActionType: BladeburnerActionEnumType; SpecialBladeburnerActionTypeForSleeve: SpecialBladeburnerActionEnumTypeForSleeve; FragmentType: FragmentEnumType; + DarknetResponseCode: DarknetResponseCodeType; }; ``` -**References:** [CityNameEnumType](./bitburner.citynameenumtype.md), [CrimeEnumType](./bitburner.crimeenumtype.md), [FactionWorkEnumType](./bitburner.factionworkenumtype.md), [GymEnumType](./bitburner.gymenumtype.md), [JobNameEnumType](./bitburner.jobnameenumtype.md), [JobFieldEnumType](./bitburner.jobfieldenumtype.md), [LocationNameEnumType](./bitburner.locationnameenumtype.md), [ToastVariantEnumType](./bitburner.toastvariantenumtype.md), [UniversityClassEnumType](./bitburner.universityclassenumtype.md), [CompanyNameEnumType](./bitburner.companynameenumtype.md), [FactionNameEnumType](./bitburner.factionnameenumtype.md), [CodingContractNameEnumType](./bitburner.codingcontractnameenumtype.md), [PositionEnumType](./bitburner.positionenumtype.md), [OrderEnumType](./bitburner.orderenumtype.md), [BladeburnerActionEnumType](./bitburner.bladeburneractionenumtype.md), [SpecialBladeburnerActionEnumTypeForSleeve](./bitburner.specialbladeburneractionenumtypeforsleeve.md), [FragmentEnumType](./bitburner.fragmentenumtype.md) +**References:** [CityNameEnumType](./bitburner.citynameenumtype.md), [CrimeEnumType](./bitburner.crimeenumtype.md), [FactionWorkEnumType](./bitburner.factionworkenumtype.md), [GymEnumType](./bitburner.gymenumtype.md), [JobNameEnumType](./bitburner.jobnameenumtype.md), [JobFieldEnumType](./bitburner.jobfieldenumtype.md), [LocationNameEnumType](./bitburner.locationnameenumtype.md), [ToastVariantEnumType](./bitburner.toastvariantenumtype.md), [UniversityClassEnumType](./bitburner.universityclassenumtype.md), [CompanyNameEnumType](./bitburner.companynameenumtype.md), [FactionNameEnumType](./bitburner.factionnameenumtype.md), [CodingContractNameEnumType](./bitburner.codingcontractnameenumtype.md), [PositionEnumType](./bitburner.positionenumtype.md), [OrderEnumType](./bitburner.orderenumtype.md), [BladeburnerActionEnumType](./bitburner.bladeburneractionenumtype.md), [SpecialBladeburnerActionEnumTypeForSleeve](./bitburner.specialbladeburneractionenumtypeforsleeve.md), [FragmentEnumType](./bitburner.fragmentenumtype.md), [DarknetResponseCodeType](./bitburner.darknetresponsecodetype.md) diff --git a/markdown/bitburner.serverauthdetails.md b/markdown/bitburner.serverauthdetails.md new file mode 100644 index 000000000..459d7b3b4 --- /dev/null +++ b/markdown/bitburner.serverauthdetails.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [ServerAuthDetails](./bitburner.serverauthdetails.md) + +## ServerAuthDetails type + +Details about a server's authentication schema + +**Signature:** + +```typescript +type ServerAuthDetails = { + isConnectedToCurrentServer: boolean; + hasSession: boolean; + modelId: string; + passwordHint: string; + data: string; + logTrafficInterval: number; + passwordLength: number; + passwordFormat: "numeric" | "alphabetic" | "alphanumeric" | "ASCII" | "unicode"; +}; +``` diff --git a/markdown/bitburner.userinterface.opentail.md b/markdown/bitburner.userinterface.opentail.md index 49bd42797..59918e30a 100644 --- a/markdown/bitburner.userinterface.opentail.md +++ b/markdown/bitburner.userinterface.opentail.md @@ -58,7 +58,7 @@ string -_(Optional)_ Optional. Hostname/IP of the script being tailed. Defaults to the server the calling script is running on. +_(Optional)_ Optional. Hostname/IP of the script being tailed. Defaults to the server this script is running on. If args are specified, this is not optional. diff --git a/markdown/bitburner.userinterface.settailfontsize.md b/markdown/bitburner.userinterface.settailfontsize.md index 89d815de4..a1d228ccc 100644 --- a/markdown/bitburner.userinterface.settailfontsize.md +++ b/markdown/bitburner.userinterface.settailfontsize.md @@ -74,7 +74,7 @@ string -_(Optional)_ Optional. Hostname/IP of the target script. Defaults to the server the calling script is running on. +_(Optional)_ Optional. Hostname/IP of the target script. Defaults to the server this script is running on. If args are specified, this is not optional. diff --git a/src/Achievements/AchievementData.json b/src/Achievements/AchievementData.json index 11994169a..096ce6aa2 100644 --- a/src/Achievements/AchievementData.json +++ b/src/Achievements/AchievementData.json @@ -136,6 +136,11 @@ "Name": "IPvGO Subnet Takeover", "Description": "Acquire SF14.1" }, + "SF15.1": { + "ID": "SF15.1", + "Name": "The Secrets of the Dark Net", + "Description": "Acquire SF15.1" + }, "MONEY_1Q": { "ID": "MONEY_1Q", "Name": "Here comes the money!", @@ -406,6 +411,11 @@ "Name": "Ten Steps Ahead", "Description": "Get a winning streak of 10 against Illuminati." }, + "DARKNET_BACKDOOR": { + "ID": "DARKNET_BACKDOOR", + "Name": "Make your Own Network", + "Description": "Install a backdoor on 50 or more darknet servers at once." + }, "CHALLENGE_BN1": { "ID": "CHALLENGE_BN1", "Name": "BN1: Challenge", @@ -461,6 +471,11 @@ "Name": "BN14: Challenge", "Description": "Destroy BN14 without making a move or cheating via IPvGO APIs." }, + "CHALLENGE_BN15": { + "ID": "CHALLENGE_BN15", + "Name": "BN15: Challenge", + "Description": "Open the cache on the final lab in the darknet." + }, "BYPASS": { "ID": "BYPASS", "Name": "Exploit: bypass", diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts index d1db7b9e7..286659aaa 100644 --- a/src/Achievements/Achievements.ts +++ b/src/Achievements/Achievements.ts @@ -37,6 +37,8 @@ import { activateSteamAchievements } from "../Electron"; import { Go } from "../Go/Go"; import { type AchievementId, type SFAchievementId, SFAchievementIds } from "./Types"; +import { getAllMovableDarknetServers } from "../DarkNet/utils/darknetNetworkUtils"; + function assertAchievements( achievements: typeof data.achievements, ): asserts achievements is AchievementDataJson["achievements"] { @@ -66,7 +68,7 @@ function assertAchievements( * - Typechecking at compile time: ID must be AchievementId, not string. * - Runtime check: The value of ID must be the same as the key of the achievement. For example, with "CYBERSEC" * achievement, the key is "CYBERSEC", so its ID must also be "CYBERSEC". - * + * * We use assertAchievements to do the runtime check and assert the type. */ const achievementData = data.achievements; @@ -582,6 +584,13 @@ export const achievements: Record = { Condition: () => false, NotInSteam: true, }, + DARKNET_BACKDOOR: { + ...achievementData.DARKNET_BACKDOOR, + Icon: "darknet-backdoor", + Visible: knowAboutBitverse, + Condition: () => getAllMovableDarknetServers().filter((s) => s.backdoorInstalled).length >= 50, + NotInSteam: true, + }, CHALLENGE_BN1: { ...achievementData.CHALLENGE_BN1, Icon: "BN1+", @@ -672,6 +681,13 @@ export const achievements: Record = { Condition: () => Player.bitNodeN === 14 && isBitNodeFinished() && !Go.moveOrCheatViaApi, NotInSteam: true, }, + CHALLENGE_BN15: { + ...achievementData.CHALLENGE_BN15, + Icon: "BN15+", + Visible: knowAboutBitverse, + Condition: () => Player.augmentations.some((a) => a.name === AugmentationName.TheSword), + NotInSteam: true, + }, BYPASS: { ...achievementData.BYPASS, Icon: "SF-1", diff --git a/src/Achievements/Types.ts b/src/Achievements/Types.ts index 6105f7368..7dabcd627 100644 --- a/src/Achievements/Types.ts +++ b/src/Achievements/Types.ts @@ -16,5 +16,6 @@ export const SFAchievementIds = [ "SF12.1", "SF13.1", "SF14.1", + "SF15.1", ] as const; export type SFAchievementId = (typeof SFAchievementIds)[number]; diff --git a/src/Augmentation/Augmentations.ts b/src/Augmentation/Augmentations.ts index 0030032d3..4725e2660 100644 --- a/src/Augmentation/Augmentations.ts +++ b/src/Augmentation/Augmentations.ts @@ -19,6 +19,7 @@ export const Augmentations: Record = (() => { "triggers feelings of admiration and approval in other people.", company_rep: 1.1, faction_rep: 1.1, + charisma_exp: 1.05, factions: [ FactionName.TianDiHui, FactionName.TheSyndicate, @@ -36,6 +37,7 @@ export const Augmentations: Record = (() => { "triggers feelings of admiration, approval, and respect in others.", company_rep: 1.2, faction_rep: 1.2, + charisma: 1.2, factions: [ FactionName.Silhouette, FactionName.FourSigma, @@ -76,6 +78,7 @@ export const Augmentations: Record = (() => { "Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " + "make conversational partners more agreeable.", stats: "This augmentation makes the Bribe minigame easier by indicating the incorrect paths.", + charisma: 1.1, isSpecial: true, factions: [FactionName.ShadowsOfAnarchy], }, @@ -514,9 +517,22 @@ export const Augmentations: Record = (() => { "millions of nanobots capable of projecting high-density muon beams, " + "creating an energy barrier around the user.", defense: 1.4, + charisma: 1.1, factions: [FactionName.Volhaven], }, // === E === // + [AugmentationName.Eloquence]: { + repCost: 2.5e4, + moneyCost: 2.5e8, + info: + "A neural implant that enhances the user's ability to resonate with others. " + + "It is capable of analyzing and interpreting the emotions of those nearby, allowing " + + "the user to better understand and influence them.", + charisma: 1.1, + crime_success: 1.1, + work_money: 1.2, + factions: [FactionName.SpeakersForTheDead], + }, [AugmentationName.EMS4Recombination]: { repCost: 2.5e3, moneyCost: 2.75e8, @@ -738,6 +754,28 @@ export const Augmentations: Record = (() => { ], }, // === G === // + [AugmentationName.Glib]: { + repCost: 4.05e4, + moneyCost: 2.5e9, + info: + "An implant that, when activated, makes the speaker sound unbelievably reasonable and plausible to listeners " + + "for the next hour. It requires no concentration from the user, and only a verbal activation component. It even affects " + + "most electronic detection methods.", + charisma_exp: 1.2, + company_rep: 1.1, + factions: [FactionName.Tetrads, FactionName.Bladeburners], + }, + [AugmentationName.GoldenTongue]: { + repCost: 1.25e5, + moneyCost: 1.25e8, + info: + "An aural implant that enhances the user's ability to communicate and persuade others. " + + "The implant uses a predictive model that lets the user say precisely what their audience " + + "wants to hear. This implant is commonly used by many high-level executives and government officials.", + charisma: 1.2, + charisma_exp: 1.3, + factions: [FactionName.SpeakersForTheDead], + }, [AugmentationName.GolemSerum]: { repCost: 3.125e4, moneyCost: 1.1e10, @@ -873,6 +911,7 @@ export const Augmentations: Record = (() => { defense: 1.08, agility: 1.08, dexterity: 1.08, + charisma: 1.08, factions: [FactionName.Tetrads, FactionName.TheDarkArmy, FactionName.TheSyndicate], }, [AugmentationName.HiveMind]: { @@ -906,6 +945,7 @@ export const Augmentations: Record = (() => { "Even though it contains no weapons, the advanced tungsten titanium " + "alloy increases the user's strength to unbelievable levels.", strength: 2.8, + charisma: 1.4, factions: [FactionName.NWO], }, [AugmentationName.HyperionV1]: { @@ -945,6 +985,7 @@ export const Augmentations: Record = (() => { dexterity: 1.4, hacking_speed: 1.03, hacking_money: 1.1, + charisma: 1.05, factions: [FactionName.BladeIndustries, FactionName.KuaiGongInternational], }, // === I === // @@ -992,6 +1033,7 @@ export const Augmentations: Record = (() => { "cells, when powered, have a negative refractive index. As a result, they bend light " + "around the skin, making the user much harder to see with the naked eye.", agility: 1.05, + charisma: 1.05, crime_money: 1.1, factions: [FactionName.SlumSnakes, FactionName.Tetrads], }, @@ -1006,10 +1048,19 @@ export const Augmentations: Record = (() => { prereqs: [AugmentationName.LuminCloaking1], agility: 1.1, defense: 1.1, + charisma_exp: 1.1, crime_money: 1.25, factions: [FactionName.SlumSnakes, FactionName.Tetrads], }, // === M === // + [AugmentationName.Magnetism]: { + repCost: 1.5e4, + moneyCost: 2.5e8, + info: "A cranial implant that increases the attractive force of the wearer. (Even its inventor isn't quite sure how it works).", + charisma: 1.1, + company_rep: 1.1, + factions: [FactionName.TheBlackHand, FactionName.TheDarkArmy], + }, [AugmentationName.MightOfAres]: { repCost: 1e4, moneyCost: 1e6, @@ -1030,6 +1081,7 @@ export const Augmentations: Record = (() => { "which improves its regenerative and extracellular homeostasis abilities.", strength: 1.2, defense: 1.2, + charisma: 1.1, factions: [ FactionName.TheDarkArmy, FactionName.TheSyndicate, @@ -1050,6 +1102,7 @@ export const Augmentations: Record = (() => { "and restructure themselves.", strength: 1.55, defense: 1.55, + charisma: 1.55, factions: [FactionName.BladeIndustries], }, [AugmentationName.NeuralAccelerator]: { @@ -1081,6 +1134,7 @@ export const Augmentations: Record = (() => { hacking_speed: 1.02, hacking_chance: 1.1, hacking_exp: 1.12, + charisma: 1.05, factions: [ FactionName.TheBlackHand, FactionName.Chongqing, @@ -1240,6 +1294,7 @@ export const Augmentations: Record = (() => { "the bloodstream to improve memory, increase focus, and provide other " + "cognitive enhancements.", company_rep: 1.2, + charisma: 1.05, factions: [ FactionName.TianDiHui, FactionName.Volhaven, @@ -1377,6 +1432,7 @@ export const Augmentations: Record = (() => { strength: 1.4, defense: 1.4, agility: 1.4, + charisma: 1.4, factions: [FactionName.KuaiGongInternational], }, [AugmentationName.PowerRecirculator]: { @@ -1400,6 +1456,16 @@ export const Augmentations: Record = (() => { charisma_exp: 1.1, factions: [FactionName.Tetrads, FactionName.TheDarkArmy, FactionName.TheSyndicate, FactionName.NWO], }, + [AugmentationName.Primer]: { + repCost: 1.875e5, + moneyCost: 3.375e9, + info: + "A cutting-edge knowledgebase entirely built off of nanotech rod-logic, training the user on social engineering. " + + "Thought to be stolen technology, its existance has been a secret until recently.", + charisma: 1.2, + charisma_exp: 1.4, + factions: [FactionName.TheDarkArmy, FactionName.TheSyndicate], + }, // === Q === // [AugmentationName.QLink]: { repCost: 1.875e6, @@ -1422,11 +1488,22 @@ export const Augmentations: Record = (() => { info: "A cranial implant that affects the user's personality, making them better " + "at negotiation in social situations.", + charisma_exp: 1.15, work_money: 1.1, company_rep: 1.15, faction_rep: 1.15, factions: [FactionName.TianDiHui], }, + [AugmentationName.SocialDynamo]: { + repCost: 2.25e5, + moneyCost: 1.2e9, + info: + "Makes the wearer a better leader and mentor by greatly increasing their awareness of social dynamics. " + + "Not actually a standard implant, but rather a series of training courses and seminars, led by a famous speaker named Denis.", + charisma: 1.3, + company_rep: 1.3, + factions: [FactionName.MegaCorp, FactionName.ECorp, FactionName.OmniTekIncorporated], + }, [AugmentationName.SPTN97]: { repCost: 1.25e6, moneyCost: 4.875e9, @@ -1451,6 +1528,7 @@ export const Augmentations: Record = (() => { "criminal organizations and allows the user to project and control holographic " + "simulacrums within a large radius. These simulacrums are commonly used for " + "espionage and surveillance work.", + charisma: 1.15, company_rep: 1.15, faction_rep: 1.15, factions: [FactionName.TheSyndicate, FactionName.TheDarkArmy, FactionName.SpeakersForTheDead], @@ -1471,7 +1549,7 @@ export const Augmentations: Record = (() => { [AugmentationName.SmartSonar]: { repCost: 2.25e4, moneyCost: 7.5e7, - info: "A cochlear implant that helps the user detect and locate enemies using sound propagation.", + info: "A cochlear implant that helps the player detect and locate enemies using sound propagation.", dexterity: 1.1, dexterity_exp: 1.15, crime_money: 1.25, @@ -1665,6 +1743,7 @@ export const Augmentations: Record = (() => { "Scientists have named these artificially enhanced units 'synfibrils'.", strength: 1.3, defense: 1.3, + charisma: 1.15, factions: [ FactionName.KuaiGongInternational, FactionName.FulcrumSecretTechnologies, @@ -1684,6 +1763,7 @@ export const Augmentations: Record = (() => { "more efficiently than an organic heart.", agility: 1.5, strength: 1.5, + charisma: 1.5, factions: [ FactionName.KuaiGongInternational, FactionName.FulcrumSecretTechnologies, @@ -1771,6 +1851,83 @@ export const Augmentations: Record = (() => { hacking_money: 1.1, factions: [FactionName.TheBlackHand], }, + [AugmentationName.TheBrokenWings]: { + repCost: 1e4, + moneyCost: 1e6, + info: + "An experimental augmentation that lets the user make incredible leaps of insight and flights of fancy. " + + "Created by a mysterious figure known only as 'The Sculptor', this augmentation appears as a set of silvery " + + "metallic patterns on the user's upper back and shoulders. " + + "Awarded to those who discover the secrets of the labyrinth.", + stats: "This augmentation increases the stasis link limit by one, and raises charisma by 15% and agility by 10%.", + charisma: 1.15, + agility: 1.1, + isSpecial: true, + factions: [], + }, + [AugmentationName.TheBoots]: { + repCost: 1e4, + moneyCost: 1e6, + info: + "Modeled after the winged boots of mythology, this implant somehow provides tireless social energy to the user. " + + "Its creator, the enigmatic Sculptor, refuses to reveal the details of how it works, and only mutters about 'liveware APIs'. " + + "Awarded to those who discover the secrets of the labyrinth.", + stats: + "This augmentation increases the speed of authentication and heartbleed by 20%, and raises charisma and dexterity by 20%.", + charisma: 1.2, + dexterity: 1.2, + isSpecial: true, + prereqs: [AugmentationName.TheBrokenWings], + factions: [], + }, + [AugmentationName.TheHammer]: { + repCost: 1e4, + moneyCost: 1e6, + info: + "This unique augmentation allows the user to strike stright to the heart of the matter and sweep aside obstacles in the way of their goals. " + + "Appearing as a simple insignia on the user's forarm, its true function is unknown. It is said to be one of the tools of The Sculptor. " + + "Awarded to those who discover the secrets of the labyrinth.", + stats: + "This augmentation increases the stasis link limit by one, and raises charisma by 35% and strength by 25%.", + charisma: 1.35, + strength: 1.25, + isSpecial: true, + prereqs: [AugmentationName.TheBoots], + factions: [], + }, + [AugmentationName.TheLaw]: { + repCost: 1e4, + moneyCost: 1e6, + info: + "An advanced neural implant that integrates Bayesian inference algorithms into the brain's decision-making processes. " + + "This augmentation enhances the user's ability to assess probabilities, predict outcomes, and adapt strategies in real-time, " + + "making them exceptionally persuasive and confident in negotiations and social interactions. " + + "Awarded to those who discover the secrets of the labyrinth.", + stats: "This augmentation raises charisma by 40%, hacking by 10%, and company rep by 5%.", + charisma: 1.4, + hacking: 1.1, + company_rep: 1.05, + isSpecial: true, + prereqs: [AugmentationName.TheHammer], + factions: [], + }, + [AugmentationName.TheSword]: { + repCost: 1e4, + moneyCost: 1e6, + info: + "A cutting-edge neural implant that leverages Solomonoff induction to analyze and predict patterns with unparalleled precision. " + + "This augmentation enhances the user's ability to deduce optimal strategies and make compelling arguments, " + + "turning every interaction into a calculated success. The technique is sometimes referred to as Solomonoff's Lightsaber, as it is a " + + "more powerful version of Occam's razor. " + + "The final augment awarded to those who discover the secrets of the labyrinth.", + stats: "This augmentation raises charisma by 50%, hacking by 16%, and company rep by 10%.", + charisma: 1.5, + hacking: 1.16, + company_rep: 1.1, + isSpecial: true, + prereqs: [AugmentationName.TheLaw], + factions: [], + }, [AugmentationName.TheRedPill]: { repCost: 2.5e6, moneyCost: 0, @@ -1797,6 +1954,7 @@ export const Augmentations: Record = (() => { "A synthetic symbiotic virus that is injected into human brain tissue. The Vangelis virus " + "heightens the senses and focus of its host while also enhancing their intuition.", dexterity_exp: 1.1, + charisma_exp: 1.1, bladeburner_analysis: 1.1, bladeburner_success_chance: 1.04, isSpecial: true, @@ -1813,6 +1971,7 @@ export const Augmentations: Record = (() => { prereqs: [AugmentationName.VangelisVirus], defense_exp: 1.1, dexterity_exp: 1.1, + charisma_exp: 1.1, bladeburner_analysis: 1.15, bladeburner_success_chance: 1.05, isSpecial: true, @@ -1858,6 +2017,17 @@ export const Augmentations: Record = (() => { isSpecial: true, factions: [FactionName.ShadowsOfAnarchy], }, + [AugmentationName.Wit]: { + repCost: 5e3, + moneyCost: 1e7, + info: + "A connective brain implant that greatly increases the user's speech reaction time. " + + "This allows the user to think faster and respond quicker in negotiations, and always have the last word.", + charisma: 1.08, + charisma_exp: 1.1, + company_rep: 1.05, + factions: [FactionName.SlumSnakes, FactionName.BitRunners], + }, // === X === // [AugmentationName.Xanipher]: { repCost: 8.75e5, diff --git a/src/Augmentation/Enums.ts b/src/Augmentation/Enums.ts index c8c992b60..5f95641df 100644 --- a/src/Augmentation/Enums.ts +++ b/src/Augmentation/Enums.ts @@ -94,6 +94,13 @@ export enum AugmentationName { HydroflameLeftArm = "Hydroflame Left Arm", BigDsBigBrain = "BigD's Big ... Brain", ZOE = "Z.O.Ë.", + Eloquence = "Eloquence Module", + GoldenTongue = "Golden Tongue Module", + Glib = "Glibness Enhancement", + Magnetism = "Magnetism Amplifier", + Primer = "The Illustrated Primer", + SocialDynamo = "Social Dynamics Processor", + Wit = "Neural Wit Amplifier", // UnnamedAug2 = "UnnamedAug2", // Bladeburner augs @@ -115,10 +122,18 @@ export enum AugmentationName { BladeArmorIPU = "BLADE-51b Tesla Armor: IPU Upgrade", BladesSimulacrum = "The Blade's Simulacrum", + // Stanek Augs StaneksGift1 = "Stanek's Gift - Genesis", StaneksGift2 = "Stanek's Gift - Awakening", StaneksGift3 = "Stanek's Gift - Serenity", + // Darknet lab augs (in order of acquisition) + TheBrokenWings = "The W1ngs of Icarus", + TheBoots = "The B00ts of Perseus", + TheHammer = "The H4mmer of Daedalus", + TheLaw = "The L4w of Bayes", + TheSword = "The B1ade of Solomonoff", + // Infiltrators MiniGames MightOfAres = "SoA - Might of Ares", // slash WisdomOfAthena = "SoA - Wisdom of Athena", // bracket diff --git a/src/BitNode/BitNode.tsx b/src/BitNode/BitNode.tsx index 47d08d652..85747804b 100644 --- a/src/BitNode/BitNode.tsx +++ b/src/BitNode/BitNode.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Player } from "@player"; -import { AugmentationName, CityName, FactionName } from "@enums"; +import { AugmentationName, CityName, CompletedProgramName, FactionName } from "@enums"; import { BitNodeMultipliers, replaceCurrentNodeMults } from "./BitNodeMultipliers"; class BitNode { @@ -194,7 +194,7 @@ export function initBitNodes() {
  • getBitNodeMultipliers() Netscript function
  • -
  • Permanent access to Formulas.exe
  • +
  • Permanent access to {CompletedProgramName.formulas}
  • Access to BitNode multiplier information on the Stats page
  • @@ -516,6 +516,49 @@ export function initBitNodes() { ), ); + BitNodes.BitNode15 = new BitNode( + 15, + "The Secrets of the Dark Net", + "The rules have changed", + ( + <> +
    + Delving into the uncharted and secretive parts of the internet comes with the promise of freedom from oppressive + authority and surveillance. Leaving stability behind and turning to the dark web comes with risks... but also + rewards. +
    +
    + Unlike the traditional network of servers, the "dark" net is a constantly shifting, complex, unreliable place + where servers can move or disappear at any moment. Long-distance communication is often impossible, requiring + scripts to be self-sufficient and durable, and spread themselves to stay alive. If you can take advantage of the + darknet servers' weak passwords and leaky logs, you will be able to gain access to the deepest parts of the + darknet and its secrets. +
    +
    + In this Bitnode, the Daedalus faction has not yet found and monopolized the fabled Red Pill augmentation. + Legends say it can be found somewhere, out there in the dark... +
    +
    + Destroying this BitNode will give you Source-File 15, or if you already have this Source-File, it will upgrade + its level up to a maximum of 3. + + ), + ( + <> + This Source-File grants the following benefits: +
      +
    • Level 1: Permanently start with the TOR router and {CompletedProgramName.darkscape}
    • +
    • + Level 2: Your charisma level increases job salary and rep gain. Also increases authentication speed by 20% +
    • +
    • + Level 3: Your charisma level increases faction work rep gain. Also increases the xp and money gained from + .cache files by 50%. +
    • +
    + + ), + ); } export const defaultMultipliers = new BitNodeMultipliers(); @@ -737,6 +780,9 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier BladeburnerRank: 0, + DarknetLabyrinthRewardsTheRedPill: 0, + DarknetMoneyMultiplier: 0, + GangSoftcap: 0, GangUniqueAugs: 0, @@ -1024,6 +1070,41 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier WorldDaemonDifficulty: 5, }); } + + case 15: { + return new BitNodeMultipliers({ + HackingLevelMultiplier: 0.6, + HackingSpeedMultiplier: 0.6, + + StrengthLevelMultiplier: 0.7, + DefenseLevelMultiplier: 0.7, + DexterityLevelMultiplier: 0.7, + AgilityLevelMultiplier: 0.7, + CharismaLevelMultiplier: 1.1, + + ServerMaxMoney: 0.8, + ServerStartingMoney: 0.5, + ServerStartingSecurity: 1.5, + + AugmentationMoneyCost: 3, + + CorporationValuation: 0.2, + CorporationSoftcap: 0.4, + CorporationDivisions: 0.4, + + DaedalusAugsRequirement: 20, + + BladeburnerRank: 0.2, + BladeburnerSkillCost: 3, + + GangUniqueAugs: 0.3, + + StaneksGiftPowerMultiplier: 0.7, + StaneksGiftExtraSize: -2, + + WorldDaemonDifficulty: 2, + }); + } default: { throw new Error("Invalid BitNodeN"); } diff --git a/src/BitNode/BitNodeMultipliers.ts b/src/BitNode/BitNodeMultipliers.ts index d2af40e12..80863ddd6 100644 --- a/src/BitNode/BitNodeMultipliers.ts +++ b/src/BitNode/BitNodeMultipliers.ts @@ -60,6 +60,12 @@ export class BitNodeMultipliers { /** Influences how many Augmentations you need in order to get invited to the Daedalus faction */ DaedalusAugsRequirement = 30; + /** If true, TRP can be found in the fourth lab deep in the darknet. */ + DarknetLabyrinthRewardsTheRedPill = 1; + + /** Influences how much money the player gains from darknet mechanics (phishing and reward caches). */ + DarknetMoneyMultiplier = 1; + /** Influences how quickly the player's defense level (not exp) scales */ DefenseLevelMultiplier = 1; diff --git a/src/BitNode/Constants.ts b/src/BitNode/Constants.ts index 1fbe265cd..f9534c5d5 100644 --- a/src/BitNode/Constants.ts +++ b/src/BitNode/Constants.ts @@ -1 +1 @@ -export const validBitNodes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; +export const validBitNodes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; diff --git a/src/BitNode/ui/BitverseRoot.tsx b/src/BitNode/ui/BitverseRoot.tsx index 259884d66..a320d781a 100644 --- a/src/BitNode/ui/BitverseRoot.tsx +++ b/src/BitNode/ui/BitverseRoot.tsx @@ -245,7 +245,7 @@ export function BitverseRoot(props: IProps): React.ReactElement { O | O O | O O | O | | / __| \ | | O - O | O | | O / | O | | O | O + O | O | | / | O | | O | O | | | | |_/ |/ | \_ \_| | | | | O | | | | | O__/ | / \__ | | O | | | O | | | | | | | / /| O / \| | | | | | | diff --git a/src/Constants.ts b/src/Constants.ts index bffbe43a1..7a25e8313 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -6,6 +6,7 @@ export const CONSTANTS = { VersionString: "3.0.0dev", isDevBranch: true, + isInTestEnvironment: globalThis.process?.env?.JEST_WORKER_ID !== undefined, VersionNumber: 44, /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience diff --git a/src/DarkNet/Constants.ts b/src/DarkNet/Constants.ts new file mode 100644 index 000000000..5d3f2017b --- /dev/null +++ b/src/DarkNet/Constants.ts @@ -0,0 +1,11 @@ +export const DarknetConstants = { + MinCyclesToProcess: 1, + MaxCyclesToProcess: 3, + DataFileSuffix: ".data.txt", + /** Discounted price when buying in Shadowed Walkway */ + DarkscapeNavigatorDiscountedPrice: 30e6, + /** Standard price when buying via darkweb */ + DarkscapeNavigatorPrice: 50e6, +} as const; + +export const MAX_PASSWORD_LENGTH = 50; diff --git a/src/DarkNet/DWRoot.tsx b/src/DarkNet/DWRoot.tsx new file mode 100644 index 000000000..8b29263fc --- /dev/null +++ b/src/DarkNet/DWRoot.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { Container } from "@mui/material"; +import { NetworkDisplayWrapper } from "./ui/NetworkDisplayWrapper"; + +export function DWRoot(): React.ReactElement { + return ( + + + + ); +} diff --git a/src/DarkNet/Enums.ts b/src/DarkNet/Enums.ts new file mode 100644 index 000000000..66835e665 --- /dev/null +++ b/src/DarkNet/Enums.ts @@ -0,0 +1,90 @@ +import type { _ValueOf, DarknetServerData } from "@nsdefs"; + +export const HORIZONTAL_CONNECTION_CHANCE = 0.5; +export const VERTICAL_CONNECTION_CHANCE = 0.3; +export const AIR_GAP_DEPTH = 8; +export const NET_WIDTH = 8; +export const MAX_NET_DEPTH = 40; +export const SERVER_DENSITY = 0.6; +export const LOW_LEVEL_SERVER_DENSITY = 0.7; +export const MS_PER_MUTATION_PER_ROW = 30_000; // 30 seconds + +// each minigame needs to have a name that sounds like a device or browser or language model and version +// (This list is not exposed to the player; they find them through discovery) +export const ModelIds = { + EchoVuln: "DeskMemo_3.1", + SortedEchoVuln: "PHP 5.4", + NoPassword: "ZeroLogon", + Captcha: "CloudBlare(tm)", + DefaultPassword: "FreshInstall_1.0", + BufferOverflow: "Pr0verFl0", + MastermindHint: "DeepGreen", + TimingAttack: "2G_cellular", + LargestPrimeFactor: "PrimeTime 2", + RomanNumeral: "BellaCuore", + DogNames: "Laika4", + GuessNumber: "AccountsManager_4.2", + CommonPasswordDictionary: "TopPass", + EUCountryDictionary: "EuroZone Free", + Yesn_t: "NIL", + BinaryEncodedFeedback: "110100100", + SpiceLevel: "RateMyPix.Auth", + ConvertToBase10: "OctantVoxel", + parsedExpression: "MathML", + divisibilityTest: "Factori-Os", + tripleModulo: "BigMo%od", + globalMaxima: "KingOfTheHill", + packetSniffer: "OpenWebAccessPoint", + encryptedPassword: "OrdoXenos", + labyrinth: "(The Labyrinth)", +} as const; + +export type MinigamesType = _ValueOf; + +export const GenericResponseMessage = { + Success: "Success", + DirectConnectionRequired: "Direct Connection Required", + AuthFailure: "Unauthorized", + NotFound: "Not Found", + RequestTimeOut: "Request Timeout", + NotEnoughCharisma: "Not Enough Charisma", + StasisLinkLimitReached: "Stasis Link Limit Reached", + NoBlockRAM: "No Host-owned RAM Left To Reallocate", + ServiceUnavailable: "Service Unavailable", +} as const; + +export const ResponseCodeEnum = { + Success: 200, + DirectConnectionRequired: 351, + AuthFailure: 401, + Forbidden: 403, + NotFound: 404, + RequestTimeOut: 408, + NotEnoughCharisma: 451, + StasisLinkLimitReached: 453, + NoBlockRAM: 454, + PhishingFailed: 455, + ServiceUnavailable: 503, +} as const; + +export const exampleDarknetServerData: DarknetServerData = { + hostname: "", + ip: "", + hasAdminRights: false, + isConnectedTo: false, + cpuCores: 1, + ramUsed: 0, + maxRam: 0, + backdoorInstalled: false, + hasStasisLink: false, + blockedRam: 0, + modelId: "", + staticPasswordHint: "", + passwordHintData: "", + difficulty: 0, + depth: -1, + requiredCharismaSkill: 0, + logTrafficInterval: -1, + isStationary: false, + purchasedByPlayer: false, +} as const; diff --git a/src/DarkNet/controllers/NetworkGenerator.ts b/src/DarkNet/controllers/NetworkGenerator.ts new file mode 100644 index 000000000..9bee40f70 --- /dev/null +++ b/src/DarkNet/controllers/NetworkGenerator.ts @@ -0,0 +1,255 @@ +import { DarknetState } from "../models/DarknetState"; +import { + AddToAllServers, + connectServers, + createUniqueRandomIp, + DeleteServer, + disconnectServers, + GetServer, +} from "../../Server/AllServers"; +import { + addGuaranteedConnection, + addRandomDarknetServers, + balanceDarknetServers, + deleteDarknetServer, + disconnectServer, +} from "./NetworkMovement"; +import { SpecialServers } from "../../Server/data/SpecialServers"; +import { Player } from "@player"; +import { Terminal } from "../../Terminal"; +import { + getLabyrinthChaRequirement, + getLabyrinthDetails, + getLabyrinthServerNames, + getNetDepth, + isLabyrinthServer, +} from "../effects/labyrinth"; +import { DarknetServer, type DarknetServerConstructorParams } from "../../Server/DarknetServer"; +import { + HORIZONTAL_CONNECTION_CHANCE, + MAX_NET_DEPTH, + ModelIds, + NET_WIDTH, + SERVER_DENSITY, + VERTICAL_CONNECTION_CHANCE, +} from "../Enums"; +import { DarknetServerOptions, DnetServerBuilder } from "../models/DarknetServerOptions"; +import { + getAllMovableDarknetServers, + getNeighborsOnRow, + getServersOnRowAbove, + getServersOnRowBelow, +} from "../utils/darknetNetworkUtils"; +import { getDarknetServer } from "../utils/darknetServerUtils"; +import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; +import { JSONMap } from "../../Types/Jsonable"; +import type { ScriptFilePath } from "../../Paths/ScriptFilePath"; +import type { Script } from "../../Script/Script"; +import type { CodingContract } from "../../CodingContract/Contract"; +import { getTorRouter } from "../../Server/ServerHelpers"; + +export function initDarkwebServer(): void { + const existingServer = GetServer(SpecialServers.DarkWeb); + if (existingServer instanceof DarknetServer) { + return; + } + let scripts = new JSONMap(); + let contracts: CodingContract[] = []; + const hasTOR = Player.hasTorRouter(); + if (existingServer) { + scripts = existingServer.scripts; + contracts = existingServer.contracts; + // Remove legacy darkweb server, so it can be made into a darknet server + disconnectServers(existingServer, Player.getHomeComputer()); + DeleteServer(existingServer.hostname); + } + + const data: DarknetServerOptions = { + password: "", + modelId: ModelIds.NoPassword, + staticPasswordHint: "There is no password", + leftOffset: -1, + depth: -1, + difficulty: 0, + }; + + const darkweb = DnetServerBuilder(data, SpecialServers.DarkWeb); + darkweb.isStationary = true; + darkweb.hasAdminRights = true; + darkweb.blockedRam = 0; + darkweb.scripts = scripts; + darkweb.contracts = contracts; + if (hasTOR) { + getTorRouter(); + } +} + +export const populateDarknet = () => { + initDarkwebServer(); + + if (getAllMovableDarknetServers().length) { + loadDarknet(); + return; + } + + clearDarknet(true); + addLabyrinth(); + addRandomDarknetServers(getNetDepth() * NET_WIDTH * SERVER_DENSITY - 10); + addRandomDarknetServers(5 - DarknetState.Network[0].length); + addRandomDarknetServers(5 - DarknetState.Network[1].length); + balanceDarknetServers(); + + const updatedServers = getAllMovableDarknetServers(); + for (let i = 0; i < getNetDepth(); i++) { + const server = updatedServers[Math.floor(Math.random() * updatedServers.length)]; + addGuaranteedConnection(server); + } +}; + +export const clearDarknet = (force = false) => { + movePlayerIfNeeded(); + for (let i = 0; i < MAX_NET_DEPTH; i++) { + for (let j = 0; j < NET_WIDTH; j++) { + const server = DarknetState.Network[i]?.[j]; + if (!server) continue; + deleteDarknetServer(server, force); + DarknetState.Network[i][j] = null; + } + } + const darkwebRoot = GetServer(SpecialServers.DarkWeb); + if (darkwebRoot) { + darkwebRoot.serversOnNetwork = [Player.getHomeComputer().hostname]; + } + + for (const lab of getLabyrinthServerNames()) { + const labyrinth = getDarknetServer(lab); + if (!labyrinth) continue; + deleteDarknetServer(labyrinth); + } + + DarknetState.zoomIndex = 7; + DarknetState.netViewLeftScroll = 0; + DarknetState.netViewTopScroll = 0; + DarknetState.allowMutating = true; + DarknetState.openServer = null; + DarknetState.stockPromotions = {}; + DarknetState.migrationInductionServers = {}; + DarknetState.serverState = {}; +}; + +export const movePlayerIfNeeded = (server?: DarknetServer) => { + const connectedServer = Player.getCurrentServer(); + if ((!server && connectedServer instanceof DarknetServer) || server?.hostname === connectedServer.hostname) { + Terminal.print(`Something seems to have happened to '${connectedServer.hostname}'...`); + Terminal.connectToServer(SpecialServers.Home); + } +}; + +/** + * Loads all the darknet servers into DarknetState.Network, if it is not already populated + */ +export const loadDarknet = () => { + const currentServers = DarknetState.Network.flat().filter((s) => s !== null && !s.isStationary); + if (currentServers.length) { + return; + } + + const darkNetServers = getAllMovableDarknetServers(); + for (const server of darkNetServers) { + if (isLabyrinthServer(server.hostname)) { + continue; + } + disconnectServer(server, true); + addServerToNetwork(server, server.depth, server.leftOffset); + } + balanceDarknetServers(); + + const updatedServers = getAllMovableDarknetServers(); + for (let i = 0; i < getNetDepth(); i++) { + const server = updatedServers[Math.floor(Math.random() * updatedServers.length)]; + addGuaranteedConnection(server); + } +}; + +export const addRandomConnections = (server: DarknetServer) => { + if (isLabyrinthServer(server.hostname)) { + return; + } + const x = server.depth; + const y = server.leftOffset; + const horizontalNeighbors = getNeighborsOnRow(x, y); + horizontalNeighbors.forEach((neighbor) => { + if (Math.random() < HORIZONTAL_CONNECTION_CHANCE) { + connectServers(server, neighbor); + } + }); + + const serversAbove = getServersOnRowAbove(x); + const serversBelow = getServersOnRowBelow(x); + [...serversAbove, ...serversBelow].forEach((neighbor) => { + const distance = Math.abs(neighbor.depth ?? x - x) + 1; + if (Math.random() < VERTICAL_CONNECTION_CHANCE / distance) { + connectServers(server, neighbor); + } + }); +}; + +export const addServerToNetwork = (server: DarknetServer, x: number, y: number) => { + if (DarknetState.Network[x][y]?.hostname) { + exceptionAlert( + `Server already exists at this coordinate. Hostname: ${DarknetState.Network[x][y].hostname}. Coordinate: ${x}-${y}`, + true, + ); + return; + } + + DarknetState.Network[x][y] = server; + server.depth = x; + server.leftOffset = y; + + addRandomConnections(server); + addGuaranteedConnection(server); + + if (server.depth === 0) { + const darkWebRoot = GetServer(SpecialServers.DarkWeb); + if (darkWebRoot) { + connectServers(server, darkWebRoot); + } + } + const maxDepth = getNetDepth(); + if (server.depth === maxDepth - 1) { + const labyrinth = getLabyrinthDetails().lab; + if (labyrinth) { + connectServers(server, labyrinth); + } + } +}; + +// Creates all the special servers for use at the bottom of the dark net +export const addLabyrinth = () => { + const commonData: Omit = { + maxRam: 128, + modelId: ModelIds.labyrinth, + staticPasswordHint: "You have discovered a dark, mysterious maze. Your footsteps echo eerily in the silence.", + passwordHintData: "", + difficulty: 10, + depth: -1, + leftOffset: -1, + hasStasisLink: false, + blockedRam: 0, + logTrafficInterval: Number.MAX_SAFE_INTEGER, + isStationary: true, + }; + + for (const hostname of getLabyrinthServerNames()) { + const passwordSalt = Math.floor(Math.random() * 10000); + const server = new DarknetServer({ + ...commonData, + hostname: hostname, + ip: createUniqueRandomIp(), + password: `!!the:masterwork:of:daedalus<${passwordSalt}>!!`, + requiredCharismaSkill: getLabyrinthChaRequirement(hostname), + }); + AddToAllServers(server); + } +}; diff --git a/src/DarkNet/controllers/NetworkMovement.ts b/src/DarkNet/controllers/NetworkMovement.ts new file mode 100644 index 000000000..f54e26bb3 --- /dev/null +++ b/src/DarkNet/controllers/NetworkMovement.ts @@ -0,0 +1,397 @@ +import { connectServers, DeleteServer, disconnectServers, GetServer } from "../../Server/AllServers"; +import { + DarknetEvents, + DarknetState, + getServerState, + storeDarknetCycles, + triggerNextUpdate, +} from "../models/DarknetState"; +import { createDarknetServer } from "./ServerGenerator"; +import { addServerToNetwork, movePlayerIfNeeded } from "./NetworkGenerator"; +import { killServerScripts } from "../../Netscript/killWorkerScript"; +import { SpecialServers } from "../../Server/data/SpecialServers"; +import { getLabyrinthServerNames, getNetDepth, isLabyrinthServer } from "../effects/labyrinth"; +import { LOW_LEVEL_SERVER_DENSITY, MAX_NET_DEPTH, NET_WIDTH, SERVER_DENSITY } from "../Enums"; +import { + getAllAdjacentNeighbors, + getAllDarknetServers, + getAllMovableDarknetServers, + getAllOpenPositions, + getBackdooredDarkwebServers, + getDarknetCyclesPerMutation, + getIslands, +} from "../utils/darknetNetworkUtils"; +import { DarknetConstants } from "../Constants"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; + +export const processDarknet = (cycles: number): void => { + storeDarknetCycles(cycles); + + if (DarknetState.storedCycles < DarknetConstants.MinCyclesToProcess) { + return; + } + + const cyclesToProcess = Math.min(DarknetState.storedCycles, DarknetConstants.MaxCyclesToProcess); + DarknetState.storedCycles -= cyclesToProcess; + + const cyclesPerMutation = getDarknetCyclesPerMutation(); + if (DarknetState.cyclesSinceLastMutation > cyclesPerMutation) { + DarknetState.cyclesSinceLastMutation = 0; + mutateDarknet(); + } +}; + +export const mutateDarknet = (): void => { + if (!DarknetState.allowMutating) { + return; + } + const servers = getAllMovableDarknetServers(); + if (servers.length === 0) { + return; + } + + // resolve pending update promise, and create a new one + triggerNextUpdate(); + + // Limit mutation speed based on size of net + const depth = getNetDepth(); + const depthSpeedFactor = 16 / depth; + if (Math.random() > depthSpeedFactor) { + return; + } + + if (Math.random() < 0.3) { + const islands = getIslands(); + const island = islands[Math.floor(Math.random() * islands.length)]; + if (island) { + moveDarknetServer(island); + } + } + + if (Math.random() < 0.3) { + // Ensure good density of low level servers for early progression + addLowLevelServersIfNeeded(); + } + + if (Math.random() < 0.1) { + // remove some servers + deleteRandomDarknetServers(Math.random() * 3 + 1); + } + + if (Math.random() < 0.1) { + // Add some servers + const serversToAdd = Math.random() * 3 + 1; + for (let i = 0; i < serversToAdd; i++) { + addRandomDarknetServers(); + } + return; + } + + if (Math.random() < 0.1) { + const backdooredServers = getBackdooredDarkwebServers(); + const server = backdooredServers[Math.floor(Math.random() * backdooredServers.length)]; + if (server) { + restartServer(server); + return; + } + } + + if (Math.random() < 0.05) { + const backdooredServers = getBackdooredDarkwebServers(); + const server = backdooredServers[Math.floor(Math.random() * backdooredServers.length)]; + if (server) { + deleteDarknetServer(server); + return; + } + } + + if (Math.random() < 0.2) { + // restart a server + restartRandomServer(); + } + + if (Math.random() < 0.5) { + moveRandomDarknetServers(3); + } + + if (Math.random() < 0.5) { + addConnectionsToRandomServer(); + return; + } + + if (Math.random() < 0.5) { + // delink all connections from a server + disconnectRandomServer(); + } + + if (Math.random() < 0.1) { + // balance network to stay at a certain density + balanceDarknetServers(); + } + validateDarknetNetwork(); + DarknetEvents.emit(); +}; + +export const restartAllDarknetServers = (): void => { + const servers = getAllMovableDarknetServers(); + for (const server of servers) { + restartServer(server); + } +}; + +export const moveRandomDarknetServers = (count = 1): void => { + for (let i = 0; i < count; i++) { + const servers = getAllMovableDarknetServers(); + if (servers.length === 0) { + break; + } + const server = servers[Math.floor(Math.random() * servers.length)]; + moveDarknetServer(server); + } +}; + +export const deleteRandomDarknetServers = (count = 1): void => { + for (let i = 0; i < count; i++) { + const servers = getAllMovableDarknetServers(); + if (servers.length === 0) { + break; + } + const serverToDelete = servers[Math.floor(Math.random() * servers.length)]; + deleteDarknetServer(serverToDelete); + } +}; + +export const deleteDarknetServer = (server: DarknetServer, force = false): void => { + if (server.hostname === SpecialServers.DarkWeb) { + exceptionAlert(new Error("Something is trying to delete darkweb"), true); + return; + } + if (isImmutable(server) && !force) { + return; + } + const isLabyrinth = isLabyrinthServer(server.hostname); + movePlayerIfNeeded(server); + killServerScripts(server, "Server shut down."); + disconnectServer(server, true); + if (DarknetState.Network[server.depth]?.[server.leftOffset]) { + DarknetState.Network[server.depth][server.leftOffset] = null; + } + if (!isLabyrinth) { + DarknetState.offlineServers.push(server.hostname); + } + DeleteServer(server.hostname); + const serverState = getServerState(server.hostname); + serverState.authenticatedPIDs = []; + serverState.serverLogs = []; +}; + +export const addRandomDarknetServers = (count = 1, difficulty?: number, fixedDepth?: boolean): void => { + for (let i = 0; i < count; i++) { + const diff = difficulty ?? Math.floor(Math.random() * getNetDepth()); + const newServer = createDarknetServer(diff, -1, -1); + const range = fixedDepth ? 0 : 3; + moveDarknetServer(newServer, range, range); + if (DarknetState.offlineServers.includes(newServer.hostname)) { + DarknetState.offlineServers = DarknetState.offlineServers.filter((s) => s !== newServer.hostname); + } + } +}; + +export const addLowLevelServersIfNeeded = (): void => { + const lowLevelServers = getAllDarknetServers().filter((s) => s.depth <= 3); + const serversConnectedToDarkweb = getAllDarknetServers().filter((s) => s.depth === 0); + if (serversConnectedToDarkweb.length <= 3) { + addRandomDarknetServers(2, 0, true); + } + if (lowLevelServers.length / (4 * NET_WIDTH) < LOW_LEVEL_SERVER_DENSITY) { + addRandomDarknetServers(2, Math.floor(Math.random() * 4)); + addLowLevelServersIfNeeded(); + } +}; + +export const balanceDarknetServers = (): void => { + const movableServers = getAllMovableDarknetServers(); + const netDepth = getNetDepth(); + if (movableServers.length > netDepth * NET_WIDTH * SERVER_DENSITY) { + const serversToRemove = movableServers.length - netDepth * NET_WIDTH * SERVER_DENSITY; + deleteRandomDarknetServers(serversToRemove); + } else { + const serversToAdd = netDepth * NET_WIDTH * SERVER_DENSITY - movableServers.length; + addRandomDarknetServers(serversToAdd); + } + addLowLevelServersIfNeeded(); +}; + +const isImmutable = (server: DarknetServer): boolean => + server === DarknetState.openServer || server.isConnectedTo || server.hasStasisLink; + +export const moveDarknetServer = ( + server: DarknetServer, + maxDepthDecrease = 3, + maxDepthIncrease = 3, + startingDepth = server.difficulty, +): boolean => { + if (server.hostname === SpecialServers.DarkWeb) { + exceptionAlert(new Error("Something is trying to move darkweb"), true); + return false; + } + if (isImmutable(server)) { + // Do not try to move the server that is open in the UI or the terminal + return false; + } + + const positionOptions = getAllOpenPositions(startingDepth - maxDepthDecrease, startingDepth + maxDepthIncrease); + if (positionOptions.length === 0) { + // If server cannot be moved, do not leave it disconnected and floating + deleteDarknetServer(server); + return false; + } + + const [newX, newY] = positionOptions[Math.floor(Math.random() * positionOptions.length)]; + disconnectServer(server, true); + + if (DarknetState.Network[server.depth]?.[server.leftOffset]) { + DarknetState.Network[server.depth][server.leftOffset] = null; + } + addServerToNetwork(server, newX, newY); + return true; +}; + +const disconnectRandomServer = (): void => { + const servers = getAllMovableDarknetServers(); + if (servers.length === 0) { + return; + } + const server = servers[Math.floor(Math.random() * servers.length)]; + disconnectServer(server); +}; + +/** + * By default, this function disconnects the specified server from its neighbors, unless the neighbor is darkweb. Use + * the second parameter to ignore the exception. We added this exception to improve the stability of the servers + * directly connected to darkweb. + */ +export const disconnectServer = (server: DarknetServer, disconnectFromDarkweb = false): void => { + if (server.hostname === SpecialServers.DarkWeb) { + exceptionAlert(new Error("Something is trying to disconnect darkweb"), true); + return; + } + if (isImmutable(server)) { + return; + } + for (const neighbor of server.serversOnNetwork) { + const connectedServer = GetServer(neighbor); + const isOkToDisconnect = disconnectFromDarkweb || connectedServer?.hostname !== SpecialServers.DarkWeb; + if (connectedServer && isOkToDisconnect) { + disconnectServers(server, connectedServer); + } + } +}; + +const restartRandomServer = (): void => { + const servers = getAllMovableDarknetServers(); + if (servers.length === 0) { + return; + } + restartServer(servers[Math.floor(Math.random() * servers.length)]); +}; + +export const restartServer = (server: DarknetServer): void => { + if (isImmutable(server)) { + return; + } + killServerScripts(server, "Server restarted."); + const serverState = getServerState(server.hostname); + serverState.authenticatedPIDs = []; + serverState.serverLogs = [{ pid: -1, message: "Server restarted." }]; + server.backdoorInstalled = false; + disconnectServer(server); + addGuaranteedConnection(server); +}; + +const addConnectionsToRandomServer = (): void => { + const servers = getAllMovableDarknetServers(); + if (servers.length === 0) { + return; + } + const server = servers[Math.floor(Math.random() * servers.length)]; + addGuaranteedConnection(server); +}; + +export const addGuaranteedConnection = (server: DarknetServer): void => { + if (isLabyrinthServer(server.hostname)) { + return; + } + + const neighbors = getAllAdjacentNeighbors(server.depth, server.leftOffset); + if (neighbors.length === 0) { + return; + } + const neighbor = neighbors[Math.floor(Math.random() * neighbors.length)]; + connectServers(server, neighbor); +}; + +export const validateDarknetNetwork = (): void => { + const servers = getAllDarknetServers(); + // The darknet should have at least darkweb and labyrinth servers. + if (servers.length < getLabyrinthServerNames().length + 1) { + exceptionAlert(new Error(`There are too few darknet servers. servers.length: ${servers.length}`), true); + } + for (const server of servers) { + if (server.depth !== -1 && DarknetState.Network[server.depth]?.[server.leftOffset]?.hostname !== server.hostname) { + exceptionAlert( + new Error( + `${server.hostname} does not exist in DarknetState.Network at [${server.depth}][${server.leftOffset}]`, + ), + true, + ); + } + for (const neighborHostname of server.serversOnNetwork) { + const neighbor = GetServer(neighborHostname); + if (!neighbor) { + exceptionAlert( + new Error( + `Found invalid neighbor dnet server. hostname: ${server.hostname}. neighbor: ${neighborHostname}. ` + + `serversOnNetwork: ${server.serversOnNetwork}`, + ), + true, + ); + continue; + } + if (!neighbor.serversOnNetwork.includes(server.hostname)) { + exceptionAlert( + new Error( + `The connection between ${server.hostname} and ${neighbor.hostname} is unidirectional. ` + + `server.serversOnNetwork: ${server.serversOnNetwork}. neighbor.serversOnNetwork: ${neighbor.serversOnNetwork}`, + ), + true, + ); + } + } + if (server.depth === 0 && !server.serversOnNetwork.includes(SpecialServers.DarkWeb)) { + exceptionAlert( + new Error( + `${server.hostname} at depth 0 does not have a connection to ${SpecialServers.DarkWeb}. ` + + `server.serversOnNetwork: ${server.serversOnNetwork}`, + ), + true, + ); + } + } + for (let i = 0; i < MAX_NET_DEPTH; i++) { + for (let j = 0; j < NET_WIDTH; j++) { + const serverInNetwork = DarknetState.Network[i]?.[j]; + if (!serverInNetwork) { + continue; + } + const server = GetServer(serverInNetwork.hostname); + if (server == null) { + exceptionAlert(new Error(`${serverInNetwork.hostname} at [${i}][${j}] does not exist in AllServers`), true); + } + if (serverInNetwork !== server) { + exceptionAlert(new Error(`Invalid darknet server instance detected at [${i}][${j}]`), true); + } + } + } +}; diff --git a/src/DarkNet/controllers/ServerGenerator.ts b/src/DarkNet/controllers/ServerGenerator.ts new file mode 100644 index 000000000..022e0370f --- /dev/null +++ b/src/DarkNet/controllers/ServerGenerator.ts @@ -0,0 +1,706 @@ +import { DnetServerBuilder } from "../models/DarknetServerOptions"; +import { + commonPasswordDictionary, + defaultSettingsDictionary, + dogNameDictionary, + EUCountries, + filler, + letters, + lettersUppercase, + numbers, +} from "../models/dictionaryData"; +import { DarknetServer } from "../../Server/DarknetServer"; +import { ModelIds, MinigamesType } from "../Enums"; +import { MAX_PASSWORD_LENGTH } from "../Constants"; +import { clampNumber } from "../../utils/helpers/clampNumber"; +import { hasFullDarknetAccess } from "../effects/effects"; + +const getRandomServerConfigBuilder = (difficulty: number) => { + const tier0Servers = [getNoPasswordConfig]; + const tier1Servers = [getEchoVulnConfig, getDefaultPasswordConfig, getCaptchaConfig]; + const tier2Servers = [getDogNameConfig, getYesn_tConfig, getBufferOverflowConfig]; + const sf15UnlockedServers = hasFullDarknetAccess() ? [getKingOfTheHillConfig, getSpiceLevelConfig] : []; + const tier3Servers = [ + getSortedEchoVulnConfig, + getMastermindHintConfig, + getRomanNumeralConfig, + getGuessNumberConfig, + getConvertToBase10Config, + getDivisibilityTestConfig, + getPacketSnifferConfig, + ...sf15UnlockedServers, + ]; + const tier4Servers = [ + getLargestPrimeFactorConfig, + getLargeDictionaryConfig, + getEuCountryDictionaryConfig, + getTimingAttackConfig, + getBinaryEncodedConfig, + getParseArithmeticExpressionConfig, + getXorMaskEncryptedPasswordConfig, + getTripleModuloConfig, + ]; + + if (difficulty <= 2) { + const serverBuilders = [...tier0Servers, ...tier1Servers]; + return serverBuilders[Math.floor(Math.random() * serverBuilders.length)]; + } + if (difficulty <= 4) { + const serverBuilders = [...tier0Servers, ...tier1Servers, ...tier1Servers, ...tier2Servers, ...tier3Servers]; + return serverBuilders[Math.floor(Math.random() * serverBuilders.length)]; + } + if (difficulty <= 8) { + const serverBuilders = [...tier1Servers, ...tier2Servers, ...tier3Servers]; + return serverBuilders[Math.floor(Math.random() * serverBuilders.length)]; + } + if (difficulty <= 18) { + const serverBuilders = [...tier2Servers, ...tier3Servers, ...tier4Servers]; + return serverBuilders[Math.floor(Math.random() * serverBuilders.length)]; + } + const serverBuilders = [...tier3Servers, ...tier4Servers]; + return serverBuilders[Math.floor(Math.random() * serverBuilders.length)]; +}; + +export const createDarknetServer = (difficulty: number, depth: number, leftOffset: number): DarknetServer => { + const cappedDifficulty = clampNumber(difficulty, 0, MAX_PASSWORD_LENGTH); + return serverFactory(getRandomServerConfigBuilder(cappedDifficulty), difficulty, depth, leftOffset); +}; + +export type ServerConfig = { + modelId: MinigamesType; + password: string; + staticPasswordHint: string; + passwordHintData?: string; +}; + +export const serverFactory = ( + serverConfigBuilder: (n: number) => ServerConfig, + difficulty: number, + depth: number, + leftOffset: number, +): DarknetServer => { + return DnetServerBuilder({ + ...serverConfigBuilder(difficulty), + difficulty, + depth, + leftOffset: leftOffset, + }); +}; + +export const getEchoVulnConfig = (__difficulty: number): ServerConfig => { + const hintTemplates = [ + "The password is", + "The PIN is", + "Remember to use", + "It's set to", + "The key is", + "The secret is", + ]; + const password = getPassword(3); + const hint = `${hintTemplates[Math.floor(Math.random() * hintTemplates.length)]} ${password}`; + return { + modelId: ModelIds.EchoVuln, + password, + staticPasswordHint: hint, + }; +}; + +export const getSortedEchoVulnConfig = (difficulty: number): ServerConfig => { + const hintTemplates = [ + "The password is shuffled", + "The key is made from", + "I accidentally sorted the password:", + "The PIN uses", + ]; + const password = getPassword(Math.min(2 + difficulty / 7, 9)); + const sortedPassword = password.split("").sort().join(""); + const hint = `${hintTemplates[Math.floor(Math.random() * hintTemplates.length)]} ${sortedPassword}`; + return { + modelId: ModelIds.SortedEchoVuln, + password, + staticPasswordHint: hint, + passwordHintData: sortedPassword, + }; +}; + +export const getDictionaryAttackConfig = ( + __difficulty: number, + dictionary: readonly string[], + hintTemplates: string[], + minigameType: MinigamesType, +): ServerConfig => { + return { + modelId: minigameType, + password: dictionary[Math.floor(Math.random() * dictionary.length)], + staticPasswordHint: hintTemplates[Math.floor(Math.random() * hintTemplates.length)], + }; +}; + +export const getNoPasswordConfig = (difficulty: number): ServerConfig => { + const hintTemplates = [ + "The password is not set", + "There is no password", + "The PIN is empty", + "Did I set a code?", + "I didn't set a password", + ]; + return getDictionaryAttackConfig(difficulty, [""], hintTemplates, ModelIds.NoPassword); +}; + +export const getDefaultPasswordConfig = (difficulty: number): ServerConfig => { + const hintTemplates = [ + "The password is the default password", + "It's still the default", + "The default password is set", + "I never changed the password", + "It's still the factory settings", + ]; + return getDictionaryAttackConfig(difficulty, defaultSettingsDictionary, hintTemplates, ModelIds.DefaultPassword); +}; + +export const getCaptchaConfig = (difficulty: number): ServerConfig => { + const password = getPassword(difficulty / 2 + 3); + const filledPassword = password + .split("") + .map((char, i) => { + if (i >= password.length - 1) { + return char; + } + return char + getFillerChars(); + }) + .join(""); + + return { + modelId: ModelIds.Captcha, + password, + staticPasswordHint: "Type the numbers to prove you are human", + passwordHintData: filledPassword, + }; +}; + +const getFillerChars = () => { + let result = ""; + const num = Math.ceil(Math.random() * 3); + for (let i = 0; i < num; i++) { + result += filler[Math.floor(Math.random() * filler.length)]; + } + return result; +}; + +export const getDogNameConfig = (difficulty: number): ServerConfig => { + const hintTemplates = ["It's my dog's name", "It's the dog's name", "my first dog's name"]; + return getDictionaryAttackConfig(difficulty, dogNameDictionary, hintTemplates, ModelIds.DogNames); +}; + +export const getMastermindHintConfig = (difficulty: number): ServerConfig => { + const alphanumeric = difficulty > 16 && Math.random() < 0.3; + const passwordLength = Math.min((alphanumeric ? -1 : 2) + difficulty / 5, 10); + return { + modelId: ModelIds.MastermindHint, + password: getPassword(passwordLength, alphanumeric), + staticPasswordHint: "Only a true master may pass", + }; +}; + +export const getTimingAttackConfig = (difficulty: number): ServerConfig => { + const hintTemplates = [ + "I thought about it for some time, but that is not the password.", + "I spent a while on it, but that's not right", + "I considered it for a bit, but that's not it", + "I spent some time on it, but that's not the password", + ]; + const alphanumeric = difficulty > 16 && Math.random() < 0.3; + const length = (alphanumeric ? 0 : 3) + difficulty / 4; + return { + modelId: ModelIds.TimingAttack, + password: getPassword(length, alphanumeric), + staticPasswordHint: hintTemplates[Math.floor(Math.random() * hintTemplates.length)], + }; +}; + +export const getRomanNumeralConfig = (difficulty: number): ServerConfig => { + const password = Math.floor(Math.random() * 10 * (10 * (difficulty + 1))); + if (difficulty < 8) { + const encodedPassword = romanNumeralEncoder(password); + return { + modelId: ModelIds.RomanNumeral, + password: `${password}`, + staticPasswordHint: `The password is the value of the number '${encodedPassword}'`, + passwordHintData: encodedPassword, + }; + } else { + const passwordRangeMin = Math.random() < 0.3 ? 0 : Math.floor(password * (Math.random() * 0.2 + 0.6)); + const passwordRangeMax = password + Math.floor(Math.random() * difficulty * 10 + 10); + const encodedMin = romanNumeralEncoder(passwordRangeMin); + const encodedMax = romanNumeralEncoder(passwordRangeMax); + const hint = `The password is between '${encodedMin}' and '${encodedMax}'`; + const hintData = `${encodedMin},${encodedMax}`; + return { + modelId: ModelIds.RomanNumeral, + password: `${password}`, + staticPasswordHint: hint, + passwordHintData: hintData, + }; + } +}; + +export const getLargestPrimeFactorConfig = (difficulty: number): ServerConfig => { + const largestPrimePasswordDetails = getLargestPrimeFactorPassword(difficulty); + return { + modelId: ModelIds.LargestPrimeFactor, + password: `${largestPrimePasswordDetails.largestPrime}`, + staticPasswordHint: `The password is the largest prime factor of ${largestPrimePasswordDetails.targetNumber}`, + passwordHintData: `${largestPrimePasswordDetails.targetNumber}`, + }; +}; + +export const getGuessNumberConfig = (difficulty: number): ServerConfig => { + const password = `${Math.floor((Math.random() * 10 * (difficulty + 3)) / 3)}`; + const maxNumber = 10 ** password.length; + return { + modelId: ModelIds.GuessNumber, + password, + staticPasswordHint: `The password is a number between 0 and ${maxNumber}`, + }; +}; + +export const getLargeDictionaryConfig = (difficulty: number): ServerConfig => { + return getDictionaryAttackConfig( + difficulty, + commonPasswordDictionary, + ["It's a common password"], + ModelIds.CommonPasswordDictionary, + ); +}; + +export const getEuCountryDictionaryConfig = (difficulty: number): ServerConfig => { + return getDictionaryAttackConfig(difficulty, EUCountries, ["My favorite EU country"], ModelIds.EUCountryDictionary); +}; + +export const getYesn_tConfig = (difficulty: number): ServerConfig => { + const password = getPassword(3 + difficulty / 2, difficulty > 8); + return { + modelId: ModelIds.Yesn_t, + password, + staticPasswordHint: "you are one who's'nt authorized", + }; +}; + +export const getBufferOverflowConfig = (): ServerConfig => { + const length = Math.floor(4 + Math.random() * 4); + const password = getPassword(length, true); + return { + modelId: ModelIds.BufferOverflow, + password, + staticPasswordHint: `Warning: password buffer is ${length} bytes`, + }; +}; + +export const getBinaryEncodedConfig = (difficulty: number): ServerConfig => { + const password = getPassword(2 + difficulty / 5, difficulty > 8); + const binaryEncodedPassword = password + .split("") + .map((char) => char.charCodeAt(0).toString(2).padStart(8, "0")) + .join(" "); + return { + modelId: ModelIds.BinaryEncodedFeedback, + password, + staticPasswordHint: "beep boop", + passwordHintData: binaryEncodedPassword, + }; +}; + +export const getXorMaskEncryptedPasswordConfig = (): ServerConfig => { + const password = getPassword(3 + Math.random() * 3, true); + let passwordWithXorMaskApplied: string; + let xorMaskStrings: string[]; + + do { + passwordWithXorMaskApplied = ""; + xorMaskStrings = []; + for (const c of password) { + const charCode = c.charCodeAt(0); + const xorMask = Math.floor(Math.random() * 32); + xorMaskStrings.push(xorMask.toString(2).padStart(8, "0")); + passwordWithXorMaskApplied += String.fromCharCode(charCode ^ xorMask); + } + // Prevent characters that would break parsing in encoded output + } while (passwordWithXorMaskApplied.includes(";") || passwordWithXorMaskApplied.includes(" ")); + + return { + modelId: ModelIds.encryptedPassword, + password, + staticPasswordHint: `XOR mask encrypted password: "${passwordWithXorMaskApplied}".`, + passwordHintData: `${passwordWithXorMaskApplied};${xorMaskStrings.join(" ")}`, + }; +}; + +export const getSpiceLevelConfig = (difficulty: number): ServerConfig => { + const password = getPassword(3 + difficulty / 3, difficulty > 8); + return { + modelId: ModelIds.SpiceLevel, + password, + staticPasswordHint: "!!🌶️!!", + }; +}; + +export const getConvertToBase10Config = (difficulty: number): ServerConfig => { + const password = Math.ceil(Math.random() * 99 * (difficulty + 1)); + const bases = [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16]; + let base = bases[Math.floor(Math.random() * bases.length)]; + if (difficulty > 12) { + base += bases[Math.floor(Math.random() * bases.length)] / 10; + } + const encodedPassword = encodeNumberInBaseN(password, base); + return { + modelId: ModelIds.ConvertToBase10, + password: `${password}`, + staticPasswordHint: `the password is the base ${base} number ${encodedPassword} in base 10`, + passwordHintData: `${base},${encodedPassword}`, + }; +}; + +export const getParseArithmeticExpressionConfig = (difficulty: number): ServerConfig => { + let expression = generateSimpleArithmeticExpression(difficulty); + const result = parseSimpleArithmeticExpression(expression); + if (difficulty > 12) { + expression = expression.replaceAll("*", "ҳ").replaceAll("/", "÷").replaceAll("+", "➕").replaceAll("-", "➖"); + } + if ((difficulty > 16 && Math.random() < 0.3) || Math.random() < 0.01) { + expression += getCodeInjection(); + } + const parenCount = expression.split("(").length - 1; + if (difficulty > 20 && Math.random() < 0.3 && parenCount > 1) { + expression = expression.replace("(", "(ns.exit(),"); + } + return { + modelId: ModelIds.parsedExpression, + password: `${result}`, + staticPasswordHint: `The password is the evaluation of this expression`, + passwordHintData: expression, + }; +}; + +export const getDivisibilityTestConfig = (difficulty: number): ServerConfig => { + const password = getPasswordMadeUpOfPrimesProduct(difficulty); + return { + modelId: ModelIds.divisibilityTest, + password: `${password}`, + staticPasswordHint: `The password is divisible by 1 ;)`, + }; +}; + +export const getTripleModuloConfig = (difficulty: number): ServerConfig => { + const password = getPassword(3 + difficulty / 5); + return { + modelId: ModelIds.tripleModulo, + password: `${password}`, + staticPasswordHint: `(password % n) % (n % 32)`, + }; +}; + +export const getKingOfTheHillConfig = (difficulty: number): ServerConfig => { + const password = getPassword(Math.min(1 + difficulty / 6, 10)); + return { + modelId: ModelIds.globalMaxima, + password, + staticPasswordHint: "Ascend the highest mountain!", + }; +}; + +export const getPacketSnifferConfig = (difficulty: number): ServerConfig => { + return { + modelId: ModelIds.packetSniffer, + password: getPassword(3 + difficulty / 3, difficulty > 8), + staticPasswordHint: "(I'm busy browsing social media at the cafe)", + }; +}; + +export const encodeNumberInBaseN = (decimalNumber: number, base: number) => { + const characters = [...numbers.split(""), ...lettersUppercase.split("")]; + let digits = Math.floor(Math.log(decimalNumber) / Math.log(base)); + let remaining = decimalNumber; + let result: string = ""; + + while (remaining >= 0.0001 || digits >= 0) { + if (digits === -1) { + result += "."; + } + const place = Math.floor(remaining / base ** digits); + result += characters[place]; + remaining -= place * base ** digits; + digits -= 1; + } + + return result; +}; + +export const parseBaseNNumberString = (numberString: string, base: number): number => { + const characters = [...numbers.split(""), ...lettersUppercase.split("")]; + let result = 0; + let index = 0; + let digit = numberString.split(".")[0].length - 1; + + while (index < numberString.length) { + const currentDigit = numberString[index]; + if (currentDigit === ".") { + index += 1; + continue; + } + result += characters.indexOf(currentDigit) * base ** digit; + index += 1; + digit -= 1; + } + + return result; +}; + +// example: 4 + 5 * ( 6 + 7 ) / 2 +export const parseSimpleArithmeticExpression = (expression: string): number => { + const tokens = cleanArithmeticExpression(expression).split(""); + + // Identify parentheses + let currentDepth = 0; + const depth = tokens.map((token) => { + if (token === "(") { + currentDepth += 1; + } else if (token === ")") { + currentDepth -= 1; + return currentDepth + 1; + } + return currentDepth; + }); + const depth1Start = depth.indexOf(1); + // find the last 1 before the first 0 after depth1Start + const firstZeroAfterDepth1Start = depth.indexOf(0, depth1Start); + const depth1End = firstZeroAfterDepth1Start === -1 ? depth.length - 1 : firstZeroAfterDepth1Start - 1; + if (depth1Start !== -1) { + const subExpression = tokens.slice(depth1Start + 1, depth1End).join(""); + const result = parseSimpleArithmeticExpression(subExpression); + tokens.splice(depth1Start, depth1End - depth1Start + 1, result.toString()); + return parseSimpleArithmeticExpression(tokens.join("")); + } + + // handle multiplication and division + let remainingExpression = tokens.join(""); + + // breakdown and explanation for this regex: https://regex101.com/r/mZhiBn/1 + const multiplicationDivisionRegex = /(-?\d*\.?\d+) *([*/]) *(-?\d*\.?\d+)/; + let match = remainingExpression.match(multiplicationDivisionRegex); + + while (match) { + const [__, left, operator, right] = match; + const result = operator === "*" ? parseFloat(left) * parseFloat(right) : parseFloat(left) / parseFloat(right); + const resultString = Math.abs(result) < 0.000001 ? result.toFixed(20) : result.toString(); + remainingExpression = remainingExpression.replace(match[0], resultString); + match = remainingExpression.match(multiplicationDivisionRegex); + } + + // handle addition and subtraction + const additionSubtractionRegex = /(-?\d*\.?\d+) *([+-]) *(-?\d*\.?\d+)/; + match = remainingExpression.match(additionSubtractionRegex); + + while (match) { + const [__, left, operator, right] = match; + const result = operator === "+" ? parseFloat(left) + parseFloat(right) : parseFloat(left) - parseFloat(right); + remainingExpression = remainingExpression.replace(match[0], result.toString()); + match = remainingExpression.match(additionSubtractionRegex); + } + + const [__, leftover] = remainingExpression.match(/(-?\d*\.?\d+)/) ?? ["", ""]; + + return parseFloat(leftover); +}; + +export const generateSimpleArithmeticExpression = (difficulty: number): string => { + const operators = ["+", "-", "*", "/"]; + const operatorCount = Math.floor(difficulty / 4); + const expression = []; + for (let i = 0; i < operatorCount; i++) { + expression.push(Math.ceil(Math.random() * 98)); + expression.push(operators[Math.floor(Math.random() * operators.length)]); + + if (difficulty > 5 && Math.random() < difficulty / (difficulty + 50)) { + expression.push("("); + expression.push(generateSimpleArithmeticExpression(difficulty / 2)); + expression.push(")"); + expression.push(operators[Math.floor(Math.random() * operators.length)]); + } + } + expression.push(Math.ceil(Math.random() * 98)); + + const result = expression.join(" "); + + try { + const calc = parseSimpleArithmeticExpression(cleanArithmeticExpression(result)); + if (Math.abs(calc) < 0.1) { + return generateSimpleArithmeticExpression(difficulty); + } + } catch (__) { + return generateSimpleArithmeticExpression(difficulty); + } + + if (difficulty > 18) { + return result.replaceAll("*", "ҳ").replaceAll("/", "÷").replaceAll("+", "➕").replaceAll("-", "➖"); + } + + return result; +}; + +export const cleanArithmeticExpression = (expression: string): string => { + const expressionWithFixedSymbols = expression + .replaceAll("ҳ", "*") + .replaceAll("÷", "/") + .replaceAll("➕", "+") + .replaceAll("➖", "-") + .replaceAll("ns.exit(),", ""); + return expressionWithFixedSymbols.split(",")[0]; +}; + +const getCodeInjection = () => { + return ` , !globalThis.pwn3d && (globalThis.pwn3d=true, alert("You've been hacked! You evaluated a string and let me inject code, didn't you? HAHAHAHA!") , globalThis.openDevMenu() ) , ns.exit()`; +}; + +export const getPassword = (length: number, allowLetters = false): string => { + const characters = numbers + (allowLetters ? letters : ""); + let password = ""; + const cappedLength = clampNumber(length, 1, MAX_PASSWORD_LENGTH); + for (let i = 0; i < cappedLength; i++) { + password += characters[Math.floor(Math.random() * characters.length)]; + } + if (!allowLetters && Number(password) > Number.MAX_SAFE_INTEGER) { + password = password.slice(0, 15); + } + // prevent leading zeros in multi-digit numeric passwords + if (!allowLetters) { + return Number(password).toString(); + } + return password; +}; + +export const getPasswordType = (password: string): "numeric" | "alphabetic" | "alphanumeric" | "ASCII" | "unicode" => { + const passwordArr = password.split(""); + + if (passwordArr.every((char) => numbers.includes(char))) { + return "numeric"; + } + if (passwordArr.every((char) => letters.includes(char))) { + return "alphabetic"; + } + if (passwordArr.every((char) => numbers.includes(char) || letters.includes(char))) { + return "alphanumeric"; + } + if (passwordArr.every((char) => char.charCodeAt(0) < 128)) { + return "ASCII"; + } + return "unicode"; +}; + +export const romanNumeralEncoder = (input: number): string => { + const romanNumerals: { [key: number]: string } = { + 1: "I", + 4: "IV", + 5: "V", + 9: "IX", + 10: "X", + 40: "XL", + 50: "L", + 90: "XC", + 100: "C", + 400: "CD", + 500: "D", + 900: "CM", + 1000: "M", + }; + + const keys = Object.keys(romanNumerals).map((key) => Number(key)); + let result = ""; + for (let i = keys.length - 1; i >= 0; i--) { + const key = keys[i]; + while (input >= key) { + result += romanNumerals[key]; + input -= key; + } + } + return result || "nulla"; +}; + +export const romanNumeralDecoder = (input: string): number => { + if (input.toLowerCase() === "nulla") { + return 0; + } + + const romanToInt: { [key: string]: number } = { + I: 1, + V: 5, + X: 10, + L: 50, + C: 100, + D: 500, + M: 1000, + }; + let total = 0; + let prevValue = 0; + + for (let i = input.length - 1; i >= 0; i--) { + const currentValue = romanToInt[input[i]]; + if (currentValue < prevValue) { + total -= currentValue; + } else { + total += currentValue; + } + prevValue = currentValue; + } + + return total; +}; + +export const smallPrimes = [ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, +]; +export const largePrimes = [ + 1069, 1409, 1471, 1567, 1597, 1601, 1697, 1747, 1801, 1889, 1979, 1999, 2063, 2207, 2371, 2503, 2539, 2693, 2741, + 2753, 2801, 2819, 2837, 2909, 2939, 3169, 3389, 3571, 3761, 3881, 4217, 4289, 4547, 4729, 4789, 4877, 4943, 4951, + 4957, 5393, 5417, 5419, 5441, 5519, 5527, 5647, 5779, 5881, 6007, 6089, 6133, 6389, 6451, 6469, 6547, 6661, 6719, + 6841, 7103, 7549, 7559, 7573, 7691, 7753, 7867, 8053, 8081, 8221, 8329, 8599, 8677, 8761, 8839, 8963, 9103, 9199, + 9343, 9467, 9551, 9601, 9739, 9749, 9859, +]; + +const getLargestPrimeFactorPassword = (difficulty = 1) => { + const factorCount = 1 + Math.min(5, Math.floor(difficulty / 3)); + + const largePrimeIndex = 2 + Math.floor(Math.random() * (largePrimes.length - 2)); + const largestPrime = largePrimes[largePrimeIndex]; + let number = largestPrime; + for (let i = 1; i <= factorCount; i++) { + number *= smallPrimes[Math.floor(Math.random() * smallPrimes.length)]; + } + + return { + largestPrime: largestPrime, + targetNumber: number, + }; +}; + +const getPasswordMadeUpOfPrimesProduct = (difficulty = 1) => { + const scale = Math.min(difficulty / 2, 15); + let password; + + do { + password = BigInt(Math.floor(Math.random() * 5 * (scale + 1)) + 1); + for (let i = 0; i < scale / 3; i++) { + if (Math.random() < 0.5) { + password *= BigInt(Math.ceil(Math.random() * 5)); + } else { + password *= BigInt(smallPrimes[Math.floor(Math.random() * smallPrimes.length)]); + } + } + if (difficulty > 12) { + password *= BigInt(largePrimes[Math.floor(Math.random() * largePrimes.length)]); + } + if (difficulty > 24) { + password *= BigInt(largePrimes[Math.floor(Math.random() * largePrimes.length)]); + } + } while (BigInt(Number(password)) !== password); // ensure it fits in JS number precision + return password.toString(); +}; diff --git a/src/DarkNet/effects/SaveLoad.ts b/src/DarkNet/effects/SaveLoad.ts new file mode 100644 index 000000000..7a8dc78a8 --- /dev/null +++ b/src/DarkNet/effects/SaveLoad.ts @@ -0,0 +1,30 @@ +import { DarknetState } from "../models/DarknetState"; +import { assertObject } from "../../utils/TypeAssertion"; + +export type DarknetSaveFormat = { + storedCycles: number; +}; + +export function getDarkNetSave(): DarknetSaveFormat { + return { + storedCycles: Math.floor(DarknetState.storedCycles), + }; +} + +export function loadDarkNet(saveString: unknown): void { + if (saveString == null || typeof saveString !== "string" || saveString === "") { + return; + } + try { + const parsedData: unknown = JSON.parse(saveString); + assertObject(parsedData); + const { storedCycles } = parsedData; + if (typeof storedCycles !== "number" || !Number.isFinite(storedCycles)) { + throw new Error(`Invalid storedCycles: ${storedCycles}`); + } + DarknetState.storedCycles = storedCycles < 0 ? 0 : storedCycles; + } catch (error) { + console.error(error); + console.error("Invalid DarkNet data:", saveString); + } +} diff --git a/src/DarkNet/effects/authentication.ts b/src/DarkNet/effects/authentication.ts new file mode 100644 index 000000000..77b4aa6ba --- /dev/null +++ b/src/DarkNet/effects/authentication.ts @@ -0,0 +1,223 @@ +import { handleLabyrinthPassword, isLabyrinthServer } from "./labyrinth"; +import { handleFailedAuth, handleSuccessfulAuth } from "./effects"; +import type { DarknetResult } from "@nsdefs"; +import { PasswordResponse } from "../models/DarknetServerOptions"; +import { logPasswordAttempt } from "../models/packetSniffing"; +import { getServerState } from "../models/DarknetState"; +import { GenericResponseMessage, ModelIds, ResponseCodeEnum } from "../Enums"; +import { + getExactCorrectChars, + getExactCorrectCharsCount, + getFailureResponse, + getGenericSuccess, + getMisplacedCorrectCharsCount, +} from "../utils/darknetAuthUtils"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { SpecialServers } from "../../Server/data/SpecialServers"; +import { WHRNG } from "../../Casino/RNG"; + +export const checkPassword = ( + server: DarknetServer, + attemptedPassword: string, + pid: number, + responseTime = 0, +): PasswordResponse => { + if (isLabyrinthServer(server.hostname)) { + return handleLabyrinthPassword(attemptedPassword, server, pid); + } + + if (server.password === attemptedPassword) { + return getGenericSuccess(attemptedPassword); + } + + switch (server.modelId) { + case ModelIds.MastermindHint: { + const { exactCharacters, misplacedCharacters } = getMastermindResponse(server.password, attemptedPassword); + const exactCharsMessage = `${exactCharacters} symbol${exactCharacters == 1 ? " is" : "s are"} match exactly`; + const misplacedCharsMessage = `${misplacedCharacters} symbol${misplacedCharacters == 1 ? "" : "s"} match but ${ + misplacedCharacters == 1 ? "is" : "are" + } in the wrong place`; + const message = `Hint: ${exactCharsMessage}, and ${misplacedCharsMessage}.`; + return getFailureResponse(attemptedPassword, message, `${exactCharacters},${misplacedCharacters}`); + } + case ModelIds.GuessNumber: { + const hintData = Number(attemptedPassword) > Number(server.password) ? "Lower" : "Higher"; + return getFailureResponse(attemptedPassword, server.staticPasswordHint, hintData); + } + case ModelIds.RomanNumeral: { + const hintData = Number(attemptedPassword) > Number(server.password) ? "ALTUS NIMIS" : "PARUM BREVIS"; + return getFailureResponse(attemptedPassword, server.staticPasswordHint, hintData); + } + case ModelIds.Yesn_t: { + const response = attemptedPassword + .split("") + .map((char, i) => (char === server.password[i] ? "yes" : "yesn't")) + .join(","); + return getFailureResponse(attemptedPassword, "that wasn't right", response); + } + case ModelIds.SpiceLevel: { + const exactChars = getExactCorrectChars(server.password, attemptedPassword); + const pepperRepresentation = exactChars.map((val) => (val ? "🌶️" : "")).join("") || "0"; + return getFailureResponse( + attemptedPassword, + "Not spicy enough", + `${pepperRepresentation}/${server.password.length}`, + ); + } + case ModelIds.divisibilityTest: { + const password = Number(server.password); + const attemptedDivisor = Number(attemptedPassword); + if (isNaN(+attemptedPassword) || password % attemptedDivisor || attemptedPassword === "") { + return getFailureResponse(attemptedPassword, `Password is not divisible by '${attemptedPassword}'`, "false"); + } + return getFailureResponse(attemptedPassword, `Password IS divisible by '${attemptedPassword}'`, "true"); + } + case ModelIds.tripleModulo: { + const password = Number(server.password); + const input = Number(attemptedPassword); + const result = (password % input) % (((input - 1) % 32) + 1); + const message = + input % 32 === 0 + ? `(Password % ${input}) % 32 = ${result}` + : `(Password % ${input}) % (${input} % 32) = ${result}`; + + return getFailureResponse(attemptedPassword, message, result.toString()); + } + case ModelIds.ConvertToBase10: + case ModelIds.parsedExpression: { + const parsedAttemptedPassword = parseFloat(attemptedPassword); + if (!isNaN(parsedAttemptedPassword) && isCloseToCorrectPassword(server.password, parsedAttemptedPassword)) { + // ignore small rounding errors during floating point operations + return getGenericSuccess(attemptedPassword); + } + return getFailureResponse(attemptedPassword, server.staticPasswordHint, server.passwordHintData); + } + case ModelIds.TimingAttack: { + const indexOfDifference = server.password.split("").findIndex((char, i) => char !== attemptedPassword[i]); + const hint = `Found a mismatch while checking each character (${indexOfDifference})`; + const data = `Response time: ${responseTime}ms`; + return getFailureResponse(attemptedPassword, hint, data); + } + case ModelIds.BufferOverflow: { + // If the attempted password is longer than the server's actual password, it starts overwriting the + // part of the "buffer" that holds the expected password, which can "trick" the comparison into matching + const maskCharacter = attemptedPassword === "■".repeat(server.password.length) ? "?" : "■"; + const buffer = "ˍ".repeat(server.password.length) + maskCharacter.repeat(server.password.length); + const overwrittenBuffer = attemptedPassword.slice(0, buffer.length) + buffer.slice(attemptedPassword.length); + + const receivedBuffer = overwrittenBuffer.slice(0, server.password.length); + const expectedValueBuffer = overwrittenBuffer.slice(server.password.length); + + if (receivedBuffer === expectedValueBuffer) { + return getGenericSuccess(attemptedPassword); + } + + const failureMessage = `auth failed: received '${receivedBuffer}', expected '${expectedValueBuffer}'`; + const data = `${receivedBuffer},${expectedValueBuffer}`; + return getFailureResponse(attemptedPassword, failureMessage, data); + } + case ModelIds.globalMaxima: { + const altitude = getKingOfTheHillAltitude(server, attemptedPassword); + return getFailureResponse( + attemptedPassword, + `current altitude: ${altitude.toFixed(5)} m; highest peak: 10,000 m`, + `${altitude}`, + ); + } + default: + return getFailureResponse(attemptedPassword, server.staticPasswordHint, server.passwordHintData); + } +}; + +export const isCloseToCorrectPassword = ( + correctPassword: string, + attemptedPassword: number, + logIfNotClose = false, +): boolean => { + const difference = Math.abs(attemptedPassword - Number(correctPassword)); + const result = difference < 0.01 || difference / Number(correctPassword) < 0.005; + + if (logIfNotClose && !result) { + console.warn(`Attempted password ${attemptedPassword} is not close enough to correct password ${correctPassword}`); + } + return result; +}; + +export const getAuthResult = ( + server: DarknetServer, + attemptedPassword: string, + threads = 1, + responseTime = 0, + pid = -1, + logActivity = true, +): { result: DarknetResult; response: PasswordResponse } => { + const response = checkPassword(server, attemptedPassword, pid, responseTime); + if (logActivity) { + logPasswordAttempt(server, response, pid); + } + if (response.code === ResponseCodeEnum.Success) { + handleSuccessfulAuth(server, threads, pid); + return { + result: { + success: true, + code: ResponseCodeEnum.Success, + message: GenericResponseMessage.Success, + }, + response: response, + }; + } + handleFailedAuth(server, threads); + return { + result: { + success: false, + code: ResponseCodeEnum.AuthFailure, + message: GenericResponseMessage.AuthFailure, + }, + response: response, + }; +}; + +export const isAuthenticated = (server: DarknetServer, pid: number): boolean => { + if (!server.hasAdminRights) { + return false; + } + const serverState = getServerState(server.hostname); + return serverState.authenticatedPIDs.includes(pid) || server.hostname === SpecialServers.DarkWeb; +}; + +export const getMastermindResponse = (password: string, attemptedPassword: string) => { + return { + exactCharacters: getExactCorrectCharsCount(password, attemptedPassword), + misplacedCharacters: getMisplacedCorrectCharsCount(password, attemptedPassword), + }; +}; + +export const getKingOfTheHillAltitude = (server: DarknetServer, attemptedPassword: string) => { + const password = Number(server.password); + const x = Number(attemptedPassword); + const rng = new WHRNG(password); + const hillCount = Math.min(Math.floor(server.difficulty / 8), 4) * 2 + 1; + const passwordHillIndex = Math.floor(rng.random() * (hillCount - 2)) + 1; + const width = 10 ** Math.max(server.password.length - 2, 0) + 1; + + // As the player gets very close to the password, only consider the main hill + // This is to ensure the data is very clean as they approach, and that the peak is not + // moved off of the password value by some of the side hills + if (Math.abs((x - password) / password) < 0.03) { + return getAltitudeGivenHillSpecs(x, password, 10000, width); + } + + // Otherwise, give them the full graph result with all hills + let altitude = 0; + for (let i = 0; i < hillCount; i++) { + const locationOffset = (i - passwordHillIndex) * width * 3 * (rng.random() * 0.2 + 0.9); + const heightOffset = Math.abs((i - passwordHillIndex) * 2600) * (rng.random() * 0.1 + 0.95); + altitude += getAltitudeGivenHillSpecs(x, password + locationOffset, 10000 - heightOffset, width); + } + + return altitude; +}; + +const getAltitudeGivenHillSpecs = (x: number, location: number, height: number, width: number) => { + return height * Math.exp(((x - location) ** 2 / width ** 2) * -1); +}; diff --git a/src/DarkNet/effects/cacheFiles.ts b/src/DarkNet/effects/cacheFiles.ts new file mode 100644 index 000000000..89a20bdc1 --- /dev/null +++ b/src/DarkNet/effects/cacheFiles.ts @@ -0,0 +1,141 @@ +import { tryGeneratingRandomContract } from "../../CodingContract/ContractGenerator"; +import { Player } from "@player"; +import { formatMoney, formatNumber } from "../../ui/formatNumber"; +import { getLabAugReward, isLabyrinthServer, LAB_CACHE_NAME } from "./labyrinth"; +import { SnackbarEvents } from "../../ui/React/Snackbar"; +import { AugmentationName, CompletedProgramName, ToastVariant } from "@enums"; +import { currentNodeMults } from "../../BitNode/BitNodeMultipliers"; +import { CreateProgramWork } from "../../Work/CreateProgramWork"; +import { initStockMarket, isStockMarketInitialized } from "../../StockMarket/StockMarket"; +import { cachePrefixes } from "../models/dictionaryData"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { type CacheFilePath, resolveCacheFilePath } from "../../Paths/CacheFilePath"; +import type { CacheResult, Result } from "@nsdefs"; + +export const generateCacheFilename: (prefix?: string) => CacheFilePath | null = (prefix) => { + const filenamePrefix = prefix ?? cachePrefixes[Math.floor(Math.random() * cachePrefixes.length)]; + return resolveCacheFilePath(`${filenamePrefix}_${Math.random().toString().substring(2, 5)}.cache`); +}; + +export const addCacheToServer: (server: DarknetServer, prefix?: string) => Result<{ cacheFilename: CacheFilePath }> = ( + server, + prefix, +) => { + const cacheFilename = generateCacheFilename(prefix); + if (!cacheFilename) { + return { success: false, message: `Cannot generate path. prefix: ${prefix}` }; + } + server.caches.push(cacheFilename); + return { success: true, cacheFilename }; +}; + +export const getRewardFromCache = (server: DarknetServer, cacheName: string, suppressToast = false): CacheResult => { + const difficulty = server.difficulty; + const karmaLoss = difficulty + 1; + Player.karma -= karmaLoss; + if (isLabyrinthServer(server.hostname) && cacheName.includes(LAB_CACHE_NAME)) { + const labReward = getLabReward(); + return { + success: true, + message: labReward, + karmaLoss: -karmaLoss, + }; + } + + const rewards = [getMoneyReward, getXpReward, getProgramAndStockMarketRelatedRewards, getCCTReward]; + const reward = rewards[Math.floor(Math.random() * rewards.length)]; + const result = reward(difficulty); + + if (!suppressToast) { + SnackbarEvents.emit( + // Karma is only useful in relation to gangs, so we only show the karma loss if the player has started unlocking gang + // content. This is to avoid cluttering the UI with unnecessary info, and confusion before players discover karma. + result + (Player.isAwareOfGang() ? ` Gained -${karmaLoss} karma.` : ""), + ToastVariant.SUCCESS, + 4000, + ); + } + return { + success: true, + message: result, + karmaLoss: -karmaLoss, + }; +}; + +export const getCCTReward = (): string => { + const contractCount = [2, 3, 4][Math.floor(Math.random() * 3)]; + tryGeneratingRandomContract(contractCount); + return `New coding contracts are now available on the network!`; +}; + +export const getMoneyReward = (difficulty: number): string => { + const sf15_3Factor = Player.activeSourceFileLvl(15) > 3 ? 1.5 : 1; + const reward = + 1.2 ** difficulty * + 1e7 * + ((200 + Player.skills.charisma) / 200) * + sf15_3Factor * + Player.mults.crime_money * + currentNodeMults.DarknetMoneyMultiplier; // TODO: adjust balance + Player.gainMoney(reward, "darknet"); + return `You have discovered a cache with ${formatMoney(reward)}.`; +}; + +export const getXpReward = (difficulty: number): string => { + const sf15_3Factor = Player.activeSourceFileLvl(15) > 3 ? 1.5 : 1; + const reward = 1.2 ** difficulty * 500 * sf15_3Factor * Player.mults.charisma_exp; // TODO: adjust balance + Player.gainCharismaExp(reward); + return `You have discovered a cache with ${formatNumber(reward, 0)} cha XP.`; +}; + +export const getProgramAndStockMarketRelatedRewards = (difficulty: number): string => { + const creatingProgram = Player.currentWork instanceof CreateProgramWork ? Player.currentWork.programName : null; + const programs = [ + CompletedProgramName.serverProfiler, + CompletedProgramName.bruteSsh, + CompletedProgramName.deepScan1, + CompletedProgramName.ftpCrack, + CompletedProgramName.autoLink, + CompletedProgramName.relaySmtp, + CompletedProgramName.deepScan2, + CompletedProgramName.httpWorm, + CompletedProgramName.sqlInject, + CompletedProgramName.formulas, + ]; + + for (const program of programs) { + if (!Player.hasProgram(program) && creatingProgram !== program) { + Player.getHomeComputer().pushProgram(program); + return `You have discovered the program ${program}.`; + } + } + if (!Player.hasWseAccount) { + Player.hasWseAccount = true; + if (!isStockMarketInitialized()) { + initStockMarket(); + } + return `You have discovered a stolen WSE Account!`; + } + if (!Player.hasTixApiAccess) { + Player.hasTixApiAccess = true; + if (!isStockMarketInitialized()) { + initStockMarket(); + } + return `You have discovered a stolen TIX API access point!`; + } + if (!Player.has4SData && Player.bitNodeN !== 8 && !Player.bitNodeOptions.disable4SData) { + Player.has4SData = true; + return `You have discovered a cache of stolen 4S Data!`; + } + + return getXpReward(difficulty); +}; + +const getLabReward = (): string => { + let reward = getLabAugReward(); + if (!reward || Player.hasAugmentation(reward)) { + reward = AugmentationName.NeuroFluxGovernor; + } + Player.queueAugmentation(reward); + return `You have discovered a cache with the augmentation ${reward}!`; +}; diff --git a/src/DarkNet/effects/effects.ts b/src/DarkNet/effects/effects.ts new file mode 100644 index 000000000..2281f7af2 --- /dev/null +++ b/src/DarkNet/effects/effects.ts @@ -0,0 +1,295 @@ +import { Player } from "@player"; +import type { DarknetServerData, Person as IPerson } from "@nsdefs"; +import { AugmentationName, CompletedProgramName, LiteratureName } from "@enums"; +import { generateContract } from "../../CodingContract/ContractGenerator"; +import { + commonPasswordDictionary, + notebookFileNames, + packetSniffPhrases, + passwordFileNames, +} from "../models/dictionaryData"; +import { hintLiterature } from "../models/hintNotes"; +import { resolveTextFilePath, type TextFilePath } from "../../Paths/TextFilePath"; +import { moveDarknetServer } from "../controllers/NetworkMovement"; +import { calculateIntelligenceBonus } from "../../PersonObjects/formulas/intelligence"; +import { addSessionToServer, DarknetState, hasDarknetBonusTime } from "../models/DarknetState"; +import { DarknetServer } from "../../Server/DarknetServer"; +import { GenericResponseMessage, ModelIds, NET_WIDTH, ResponseCodeEnum } from "../Enums"; +import { addCacheToServer } from "./cacheFiles"; +import { populateDarknet } from "../controllers/NetworkGenerator"; +import { getDarknetServer } from "../utils/darknetServerUtils"; +import { + getAllMovableDarknetServers, + getBackdooredDarkwebServers, + getNearbyNonEmptyPasswordServer, + getStasisLinkServers, +} from "../utils/darknetNetworkUtils"; +import { getSharedChars, getTwoCharsInPassword } from "../utils/darknetAuthUtils"; +import { getTorRouter } from "../../Server/ServerHelpers"; +import { DarknetConstants } from "../Constants"; +import { GetServer } from "../../Server/AllServers"; +import { isLabyrinthServer } from "./labyrinth"; +import { NetscriptContext } from "../../Netscript/APIWrapper"; +import { helpers } from "../../Netscript/NetscriptHelpers"; + +export const handleSuccessfulAuth = (server: DarknetServer, threads: number, pid: number) => { + Player.gainCharismaExp(calculatePasswordAttemptChaGain(server, threads, true)); + addSessionToServer(server, pid); + + if (server.hasAdminRights) return; + + server.hasAdminRights = true; + addClue(server); + + // TODO: balance coding contract chance + if (Math.random() < 0.1 && server.difficulty > 2) { + generateContract({ server: server.hostname }); + } + + // TODO: balance cache chance + const chance = 0.1 * 1.05 ** server?.difficulty; + if (Math.random() < chance && !isLabyrinthServer(server.hostname)) { + addCacheToServer(server); + } +}; + +export const handleFailedAuth = (server: DarknetServer, threads: number) => { + Player.gainCharismaExp(calculatePasswordAttemptChaGain(server, threads, false)); +}; + +/** + * Returns the time it takes to authenticate on a server in milliseconds + * @param darknetServerData - the target server to attempt a password on + * @param person - the player's character + * @param attemptedPassword - the password being attempted + * @param threads - the number of threads used for the password attempt (which speeds up the process) + */ +export const calculateAuthenticationTime = ( + darknetServerData: DarknetServerData, + person: IPerson = Player, + threads = 1, + attemptedPassword = "", +) => { + const chaRequired = darknetServerData.requiredCharismaSkill; + const difficulty = darknetServerData.difficulty; + + const baseDiff = (difficulty + 1) * 100; + const diffFactor = 5; + const baseTime = 500; + + const threadsFactor = 1 / (1 + 0.2 * (threads - 1)); + const skillFactor = (diffFactor * chaRequired + baseDiff) / (person.skills.charisma + 100); + const noobFactor = Math.min(0.5 + difficulty / 4, 1); + const backdoorFactor = getBackdoorAuthTimeDebuff(); + const underleveledFactor = + person.skills.charisma >= chaRequired ? 1 : 1.5 + (chaRequired + 50) / (person.skills.charisma + 50); + const hasBootsFactor = Player.hasAugmentation(AugmentationName.TheBoots) ? 0.8 : 1; + const hasSf15_2Factor = Player.activeSourceFileLvl(15) > 2 ? 0.8 : 1; + const bonusTimeFactor = hasDarknetBonusTime() ? 0.75 : 1; + + const time = + baseTime * + skillFactor * + noobFactor * + backdoorFactor * + underleveledFactor * + hasBootsFactor * + hasSf15_2Factor * + bonusTimeFactor * + threadsFactor; + + // We need to call GetServer and check if it's a dnet server later because this function can be called by formulas + // APIs (darknetServerData.hostname may be an invalid hostname). + const server = GetServer(darknetServerData.hostname); + const password = server instanceof DarknetServer ? server.password : ""; + // Add extra time for timing attack server, per correct character + const sharedChars = + darknetServerData.modelId === ModelIds.TimingAttack ? getSharedChars(password, attemptedPassword) : 0; + const sharedCharsExtraTime = sharedChars * 50; + + return time * calculateIntelligenceBonus(person.skills.intelligence, 0.25) + sharedCharsExtraTime; +}; + +export const getBackdoorAuthTimeDebuff = () => { + const backdooredServerCount = getBackdooredDarkwebServers().length; + const serverCount = getAllMovableDarknetServers().filter((s) => s.hasAdminRights).length; + const safeBackdoors = Math.max(serverCount / (NET_WIDTH * 3), 2); + const backdoorSurplus = Math.max(0, backdooredServerCount - safeBackdoors); + + return 1.07 ** backdoorSurplus; +}; + +/** + * Returns a small multiplier based on charisma. + * With scalar at 1 it gives ~1.2 at 2000 charisma and ~1.6 at 10000 charisma. Caps at 2.1 at infinite cha + */ +export const getMultiplierFromCharisma = (scalar = 1) => { + const charisma = Player.skills.charisma; + const growthRate = 0.0002; // Adjust this value to control the growth rate + return ( + 1 + (0.5 * (1 - Math.exp(-growthRate * charisma)) + 0.6 * (1 - Math.exp(-growthRate * 0.2 * charisma)) * scalar) + ); +}; + +// TODO: balance xp gain +export const calculatePasswordAttemptChaGain = (server: DarknetServerData, threads: number = 1, success = false) => { + const baseXpGain = 3; + const difficultyBase = 1.12; + const xpGain = baseXpGain + difficultyBase ** server.difficulty; + const alreadyHackedMult = server.hasAdminRights ? 0.2 : 1; + const successMult = success && !server.hasAdminRights ? 10 : 1; + const bonusTimeMult = hasDarknetBonusTime() ? 1.5 : 1; + return xpGain * alreadyHackedMult * successMult * bonusTimeMult * threads * Player.mults.charisma_exp; +}; + +// TODO: balance password clue spawn rate +export const addClue = (server: DarknetServer) => { + // Basic mechanics hints + if ((Math.random() < 0.7 && server.difficulty <= 3) || Math.random() < 0.1) { + const hint: LiteratureName = hintLiterature[Math.floor(Math.random() * hintLiterature.length)]; + if (hint) { + server.messages.push(hint); + } + } + + // some entries from the common password dictionary + if (Math.random() < 0.1) { + const length = 15; + const hintFileName = getClueFileName(passwordFileNames); + const start = Math.floor(Math.random() * (commonPasswordDictionary.length - length)); + const commonPasswords = commonPasswordDictionary.slice(start, start + length).join(", "); + server.writeToTextFile(hintFileName, `Some common passwords include ${commonPasswords}`); + return; + } + + // connected neighboring server's password (does not include server name) + if (Math.random() < 0.1) { + const passwordHintName = getClueFileName(passwordFileNames); + const neighboringServerName = server.serversOnNetwork.find((s) => { + const server = getDarknetServer(s); + return server && !server.hasAdminRights && server.password; + }); + const neighboringServer = neighboringServerName ? getDarknetServer(neighboringServerName) : null; + if (neighboringServer) { + server.writeToTextFile(passwordHintName, `Remember this password: ${neighboringServer.password}`); + return; + } + } + + // non-connected nearby server's password (includes server name) + if (Math.random() < 0.1) { + const hintFileName = getClueFileName(passwordFileNames); + const targetServer = getNearbyNonEmptyPasswordServer(server, true); + if (targetServer) { + const contents = `Server: ${targetServer.hostname} Password: "${targetServer.password}"`; + server.writeToTextFile(hintFileName, contents); + return; + } + } + + if (Math.random() < 0.4) { + const hintFileName = getClueFileName(notebookFileNames); + const loreNote = packetSniffPhrases[Math.floor(Math.random() * packetSniffPhrases.length)]; + server.writeToTextFile(hintFileName, loreNote); + return; + } + + if (Math.random() < 0.7) { + const hintFileName = getClueFileName(passwordFileNames); + const targetServer = getNearbyNonEmptyPasswordServer(server); + if (targetServer) { + const [containedChar1, containedChar2] = getTwoCharsInPassword(targetServer.password); + const hint = `The password for ${targetServer.hostname} contains ${containedChar1} and ${containedChar2}`; + server.writeToTextFile(hintFileName, hint); + return; + } + } +}; + +export const getClueFileName = (fileNameList: readonly string[]): TextFilePath => { + const filePath = resolveTextFilePath( + fileNameList[Math.floor(Math.random() * fileNameList.length)] + DarknetConstants.DataFileSuffix, + ); + if (!filePath) { + throw new Error(`Invalid clue file name. fileNameList: ${fileNameList}`); + } + return filePath; +}; + +export const getDarknetVolatilityMult = (symbol: string) => { + const charges = DarknetState.stockPromotions[symbol] ?? 0; + const growthRate = 0.001; + return 1 + (1 - Math.exp(-growthRate * charges) + 2 * (1 - Math.exp(-growthRate * 0.15 * charges))); +}; + +export const scaleDarknetVolatilityIncreases = (scalar: number) => { + for (const symbol in DarknetState.stockPromotions) { + if (DarknetState.stockPromotions[symbol] > 0) { + DarknetState.stockPromotions[symbol] *= scalar; + } + } +}; + +export const getStasisLinkLimit = (): number => { + const brokenWingLimitIncrease = Player.hasAugmentation(AugmentationName.TheBrokenWings) ? 1 : 0; + const hammerLimitIncrease = Player.hasAugmentation(AugmentationName.TheHammer) ? 1 : 0; + return 1 + brokenWingLimitIncrease + hammerLimitIncrease; +}; + +export const getSetStasisLinkDuration = (): number => { + return (1000 / (Player.skills.charisma + 1000)) * 30_000; +}; + +export const setStasisLink = (ctx: NetscriptContext, server: DarknetServer, shouldLink: boolean) => { + const stasisLinkCount = getStasisLinkServers().length; + const stasisLinkLimit = getStasisLinkLimit(); + if (shouldLink && stasisLinkCount >= stasisLinkLimit) { + helpers.log(ctx, () => `Stasis link limit reached. (${stasisLinkCount}/${stasisLinkLimit})`); + return { + success: false, + code: ResponseCodeEnum.StasisLinkLimitReached, + message: GenericResponseMessage.StasisLinkLimitReached, + }; + } + + server.hasStasisLink = shouldLink; + server.backdoorInstalled = shouldLink; + const message = `Stasis link ${shouldLink ? "applied to" : "removed from"} server ${server.hostname}.`; + helpers.log(ctx, () => `${message}. (${stasisLinkCount}/${stasisLinkLimit} links in use)`); + return { + success: true, + code: ResponseCodeEnum.Success, + message: GenericResponseMessage.Success, + }; +}; + +export const chargeServerMigration = (server: DarknetServer, threads = 1) => { + const chargeIncrease = ((Player.skills.charisma + 500) / (server.difficulty * 200 + 1000)) * 0.01 * threads; + const xpGained = Player.mults.charisma_exp * 50 * ((200 + Player.skills.charisma) / 200) * threads; + Player.gainCharismaExp(xpGained); + DarknetState.migrationInductionServers[server.hostname] = + (DarknetState.migrationInductionServers[server.hostname] ?? 0) + chargeIncrease; + const result = { + chargeIncrease, + newCharge: Math.min(DarknetState.migrationInductionServers[server.hostname], 1), + xpGained: xpGained, + }; + if (DarknetState.migrationInductionServers[server.hostname] >= 1) { + moveDarknetServer(server, -2, 4); + DarknetState.migrationInductionServers[server.hostname] = 0; + } + return result; +}; + +export const getDarkscapeNavigator = () => { + if (!Player.hasTorRouter()) { + getTorRouter(); + } + const existingPrograms = Player.getHomeComputer().programs; + if (!existingPrograms.includes(CompletedProgramName.darkscape)) { + Player.getHomeComputer().pushProgram(CompletedProgramName.darkscape); + } + populateDarknet(); +}; + +export const hasFullDarknetAccess = (): boolean => Player.bitNodeN === 15 || Player.activeSourceFileLvl(15) > 0; diff --git a/src/DarkNet/effects/labyrinth.ts b/src/DarkNet/effects/labyrinth.ts new file mode 100644 index 000000000..46814ac1d --- /dev/null +++ b/src/DarkNet/effects/labyrinth.ts @@ -0,0 +1,502 @@ +import { PasswordResponse } from "../models/DarknetServerOptions"; +import { addSessionToServer, DarknetState } from "../models/DarknetState"; +import { calculatePasswordAttemptChaGain, hasFullDarknetAccess } from "./effects"; +import { Player } from "@player"; +import { SpecialServers } from "../../Server/data/SpecialServers"; +import { AugmentationName } from "@enums"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { getBitNodeMultipliers } from "../../BitNode/BitNode"; +import { ResponseCodeEnum } from "../Enums"; +import { addCacheToServer } from "./cacheFiles"; +import { getDarknetServer } from "../utils/darknetServerUtils"; +import { getFriendlyType, TypeAssertionError } from "../../utils/TypeAssertion"; +import type { SuccessResult } from "@nsdefs"; + +export const LAB_CACHE_NAME = "the_great_work"; + +const NORTH = [0, -1]; +const EAST = [1, 0]; +const SOUTH = [0, 1]; +const WEST = [-1, 0]; + +const WALL = "█"; +const PATH = " "; + +const MULTI_MAZE_THRESHOLD = 5; + +type labDetails = { + name: string; + depth: number; + cha: number; + mazeWidth: number; + mazeHeight: number; + manual: boolean; +}; + +export const labData: Record = { + [SpecialServers.NormalLab]: { + name: SpecialServers.NormalLab, + depth: 7, + cha: 300, + mazeWidth: 20, + mazeHeight: 14, + manual: true, + }, + [SpecialServers.CruelLab]: { + name: SpecialServers.CruelLab, + depth: 12, + cha: 600, + mazeWidth: 30, + mazeHeight: 20, + manual: true, + }, + [SpecialServers.MercilessLab]: { + name: SpecialServers.MercilessLab, + depth: 19, + cha: 1500, + mazeWidth: 40, + mazeHeight: 26, + manual: false, + }, + [SpecialServers.UberLab]: { + name: SpecialServers.UberLab, + depth: 23, + cha: 2500, + mazeWidth: 60, + mazeHeight: 40, + manual: false, + }, + [SpecialServers.EternalLab]: { + name: SpecialServers.EternalLab, + depth: 29, + cha: 2800, + mazeWidth: 60, + mazeHeight: 40, + manual: false, + }, + [SpecialServers.FinalLab]: { + name: SpecialServers.FinalLab, + depth: 31, + cha: 3200, + mazeWidth: 60, + mazeHeight: 40, + manual: false, + }, + [SpecialServers.BonusLab]: { + name: SpecialServers.BonusLab, + depth: 31, + cha: 3200, + mazeWidth: 60, + mazeHeight: 40, + manual: false, + }, +} as const; + +/** + * Generates a maze using the stack-based iterative backtracking algorithm. + * This builds the maze by moving in random directions, removing walls as it goes through unvisited nodes. + * If it hits a dead end with only visited nodes, it backtracks to the last node with unvisited neighbors. + * @param width - the width of the maze + * @param height - the height of the maze + * @returns a 2D char array representing the maze, where "█" is a wall, " " is a path, "S" is the start, and "E" is the end + */ +export const generateMaze = (width: number = 41, height: number = 29): string[] => { + // Make a simple maze below the threshold + if (width < MULTI_MAZE_THRESHOLD) { + return mazeMaker(width, height).map((row) => row.join("")); + } + + // Stitch together 4 mazes for more interesting geometry + + const halfWidth = Math.ceil(width / 2); + const halfHeight = Math.ceil(height / 2); + + // BAbove the threshold, join together 4 mazes and make some breaks in the walls + const maze1 = mazeMaker(halfWidth, halfHeight); + const maze2 = mazeMaker(halfWidth, halfHeight); + const maze3 = mazeMaker(halfWidth, halfHeight); + const maze4 = mazeMaker(halfWidth, halfHeight); + + const resultingMazeTopHalf = maze1.map((row, y) => row.slice(0, -1).concat(maze2[y])); + const resultingMazeBottomHalf = maze3.map((row, y) => row.slice(0, -1).concat(maze4[y])); + const resultingMaze = resultingMazeTopHalf.slice(0, -1).concat(resultingMazeBottomHalf); + + const subWidth = maze1[0].length - 1; + const subHeight = maze1.length - 1; + + // Add gaps in the walls between the mazes + const randomTopGap = Math.floor((Math.random() * halfWidth) / 4) * 2 + 1; + resultingMaze[randomTopGap][subWidth] = PATH; + + const randomLeftGap = Math.floor((Math.random() * halfHeight) / 4) * 2 + 1; + resultingMaze[subHeight][randomLeftGap] = PATH; + + const randomBottomGap = (Math.floor((Math.random() * halfWidth) / 4) + 1) * 2; + resultingMaze[height - randomBottomGap - 1][subWidth] = PATH; + + const randomRightGap = (Math.floor((Math.random() * halfHeight) / 4) + 1) * 2; + resultingMaze[subHeight][width - randomRightGap - 1] = PATH; + + return resultingMaze.map((row) => row.join("")); +}; + +const mazeMaker = (setWidth: number, setHeight: number): string[][] => { + const width = setWidth % 2 === 0 ? setWidth + 1 : setWidth; + const height = setHeight % 2 === 0 ? setHeight + 1 : setHeight; + const maze: string[][] = Array.from({ length: height }, () => Array(width).fill(WALL)); + const stack: [number, number][] = []; + stack.push([1, 1]); + const directions = [NORTH, EAST, SOUTH, WEST]; + while (stack.length > 0) { + const node = stack.pop(); + if (!node?.[0] || !node[1]) throw new Error("Invalid stack pop"); + const [x, y] = node; + + const neighbors = directions + .map(([dx, dy]) => [x + dx * 2, y + dy * 2]) + .filter(([nx, ny]) => nx > 0 && nx < width && ny > 0 && ny < height && maze[ny][nx] === WALL); + + if (neighbors.length > 0) { + stack.push([x, y]); + const [nx, ny] = neighbors[Math.floor(Math.random() * neighbors.length)]; + maze[(y + ny) / 2][(x + nx) / 2] = PATH; + maze[ny][nx] = PATH; + stack.push([nx, ny]); + } + } + + return maze; +}; + +export const getSurroundingsVisualized = ( + maze: string[], + x: number, + y: number, + range = 1, + showPlayer = false, + showEnd = false, +): string => { + const result: string[] = []; + for (let i = y - range; i <= y + range; i++) { + let row = ""; + for (let j = x - range; j <= x + range; j++) { + if (i === y && j === x && showPlayer) { + row += "@"; + continue; + } + if (i === maze.length - 2 && j === maze[0].length - 2 && showEnd) { + row += "X"; + continue; + } + row += maze[i]?.[j] ?? PATH; + } + result.push(row); + } + + return result.join("\n"); +}; + +const getLocationStatus = (pid: number): LocationStatus => { + const [initialX, initialY] = DarknetState.labLocations[pid] ?? [1, 1]; + const surroundings = getSurroundingsVisualized(getLabMaze(), initialX, initialY).split("\n"); + return { + coords: [initialX, initialY], + north: surroundings[0][1] === PATH, + east: surroundings[1][2] === PATH, + south: surroundings[2][1] === PATH, + west: surroundings[1][0] === PATH, + }; +}; + +export const getLabyrinthLocationReport = (pid: number): SuccessResult => { + return { + ...getLocationStatus(pid), + success: true, + }; +}; + +export const handleLabyrinthPassword = ( + attemptedPassword: string, + server: DarknetServer, + pid: number, +): PasswordResponse => { + const labDetails = getLabyrinthDetails(); + + if (Player.skills.charisma < labDetails.cha) { + const failureMessages = [ + `You find yourself lost and confused. You need to be more charismatic to navigate the labyrinth.`, + `You stumble in the dark. You need more moxie to find your way.`, + `You feel the walls closing in. You need to be more charming to escape.`, + `You are unable to make any progress. You need more charisma to find the secret.`, + ]; + return { + passwordAttempted: attemptedPassword, + code: ResponseCodeEnum.NotEnoughCharisma, + message: failureMessages[Math.floor(Math.random() * failureMessages.length)], + }; + } + + const maze = getLabMaze(); + if (!DarknetState.labLocations[pid]) { + DarknetState.labLocations[pid] = [1, 1]; + } + const [initialX, initialY] = DarknetState.labLocations[pid]; + const end = [maze[0].length - 2, maze.length - 2]; + const [dx, dy] = getDirectionFromInput(attemptedPassword); + const newLocation: [number, number] = [initialX + dx * 2, initialY + dy * 2]; + + const labServer = labDetails.lab; + if (!labServer) { + throw new Error("Labyrinth server is missing!"); + } + + if (labServer.hasAdminRights) { + addSessionToServer(labServer, pid); + return { + passwordAttempted: attemptedPassword, + code: ResponseCodeEnum.Success, + message: "You have discovered the end the labyrinth.", + data: labServer.password, + }; + } + + if (!labServer.hasAdminRights && attemptedPassword === labServer.password) { + return { + passwordAttempted: attemptedPassword, + code: ResponseCodeEnum.AuthFailure, + message: `You have decided, after some deliberation, that the best way to beat a maze is to find the end, and not to try and skip it.`, + }; + } + const initialSurroundings = getSurroundingsVisualized(maze, initialX, initialY, 1, true, false); + + const potentialWall: [number, number] = [initialX + dx, initialY + dy]; + if (maze[potentialWall[1]]?.[potentialWall[0]] !== PATH) { + return { + passwordAttempted: attemptedPassword, + code: ResponseCodeEnum.AuthFailure, + message: `You cannot go that way. You are still at ${initialX},${initialY}.`, + data: initialSurroundings, + }; + } + + if (!dx && !dy) { + return { + passwordAttempted: attemptedPassword, + code: ResponseCodeEnum.AuthFailure, + message: `You don't know how to do that. Try a command such as "go north"`, + data: initialSurroundings, + }; + } + + DarknetState.labLocations[pid] = newLocation; + + if (newLocation[0] == end[0] && newLocation[1] == end[1]) { + Player.gainCharismaExp(calculatePasswordAttemptChaGain(server, 32, true)); + server.hasAdminRights = true; + const cacheCount = getLabyrinthDetails().name === SpecialServers.BonusLab ? 3 : 1; + for (let i = 0; i < cacheCount; i++) { + addCacheToServer(server, LAB_CACHE_NAME); + } + addSessionToServer(labServer, pid); + + return { + passwordAttempted: attemptedPassword, + code: ResponseCodeEnum.Success, + message: "You have successfully navigated the labyrinth! Congratulations", + data: labServer.password, + }; + } + + const newSurroundings = getSurroundingsVisualized(maze, newLocation[0], newLocation[1], 1, true, false); + return { + passwordAttempted: attemptedPassword, + code: ResponseCodeEnum.AuthFailure, + message: `You have moved to ${newLocation[0]},${newLocation[1]}.`, + data: newSurroundings, + }; +}; + +const getDirectionFromInput = (input: string): number[] => { + const direction = input + .split(" ") + .map((word) => getOrdinalInput(word)) + .filter((d) => d); + return direction[0] ?? [0, 0]; +}; + +const getOrdinalInput = (input: string): number[] | null => { + if (["n", "north", "up"].find((i) => input.toLowerCase().trim() === i)) { + return NORTH; + } + if (["e", "east", "right"].find((i) => input.toLowerCase().trim() === i)) { + return EAST; + } + if (["s", "south", "down"].find((i) => input.toLowerCase().trim() === i)) { + return SOUTH; + } + if (["w", "west", "left"].find((i) => input.toLowerCase().trim() === i)) { + return WEST; + } + return null; +}; + +export const getLabMaze = (): string[] => { + if (!DarknetState.labyrinth) { + const { mazeWidth, mazeHeight } = getLabyrinthDetails(); + DarknetState.labyrinth = generateMaze(mazeWidth, mazeHeight); + } + return DarknetState.labyrinth; +}; + +export const getLabyrinthServerNames = () => { + const labHostnames: string[] = Object.keys(labData); + return labHostnames; +}; + +export const getLabyrinthChaRequirement = (name: string) => { + return labData[name]?.cha ?? 0; +}; + +export const getNetDepth = () => { + const labDetails = getLabyrinthDetails(); + return labDetails.depth ?? 10; +}; + +export const isLabyrinthServer = (hostName: string) => { + const labHostnames: string[] = getLabyrinthServerNames(); + return labHostnames.includes(hostName); +}; + +export const getLabAugReward = (): AugmentationName => { + const allowTRP = getBitNodeMultipliers(Player.bitNodeN, 1).DarknetLabyrinthRewardsTheRedPill; + const augmentOrder = [ + AugmentationName.TheBrokenWings, + AugmentationName.TheBoots, + AugmentationName.TheHammer, + AugmentationName.TheLaw, + AugmentationName.TheSword, + ]; + const nextAug = augmentOrder.find((aug) => !hasAugment(aug)); + + if (!nextAug && (hasAugment(AugmentationName.TheRedPill) || !allowTRP)) { + return AugmentationName.NeuroFluxGovernor; + } + + // On BN15, the fourth lab has the Red Pill + if (Player.bitNodeN === 15 && nextAug === AugmentationName.TheLaw && !hasAugment(AugmentationName.TheRedPill)) { + return AugmentationName.TheRedPill; + } + + // On BNs that allow TRP in Lab, the sixth lab has the red pill + if (!nextAug && allowTRP) { + return AugmentationName.TheRedPill; + } + + return nextAug ?? AugmentationName.NeuroFluxGovernor; +}; + +const hasAugment = (aug: AugmentationName) => !!Player.augmentations.find((a) => a.name === aug); + +const getCurrentLabName = () => { + const allowTRP = getBitNodeMultipliers(Player.bitNodeN, 1).DarknetLabyrinthRewardsTheRedPill; + + if (!hasAugment(AugmentationName.TheBrokenWings)) { + return SpecialServers.NormalLab; + } + if (!hasAugment(AugmentationName.TheBoots)) { + return SpecialServers.CruelLab; + } + if (!hasAugment(AugmentationName.TheHammer)) { + return SpecialServers.MercilessLab; + } + if (Player.bitNodeN === 15) { + if (!hasAugment(AugmentationName.TheRedPill)) { + return SpecialServers.UberLab; + } + if (!hasAugment(AugmentationName.TheLaw)) { + return SpecialServers.EternalLab; + } + if (!hasAugment(AugmentationName.TheSword)) { + return SpecialServers.FinalLab; + } + return SpecialServers.BonusLab; + } + + if (!hasAugment(AugmentationName.TheLaw)) { + return SpecialServers.UberLab; + } + if (!hasAugment(AugmentationName.TheSword)) { + return SpecialServers.EternalLab; + } + if (allowTRP && !hasAugment(AugmentationName.TheRedPill)) { + return SpecialServers.FinalLab; + } + + return SpecialServers.BonusLab; +}; + +export const getLabyrinthDetails = (): { + lab: DarknetServer | null; + depth: number; + manual: boolean; + mazeWidth: number; + mazeHeight: number; + cha: number; + name: string; +} => { + // Lab not unlocked yet + if (!hasFullDarknetAccess()) { + return { + cha: 300, + mazeHeight: 10, + mazeWidth: 10, + name: "", + lab: null, + depth: 5, + manual: false, + }; + } + + const labName = getCurrentLabName(); + const labDetails = labData[labName]; + + return { + lab: getDarknetServer(labName), + depth: labDetails.depth, + manual: labDetails.manual, + mazeWidth: labDetails.mazeWidth, + mazeHeight: labDetails.mazeHeight, + cha: labDetails.cha, + name: labDetails.name, + }; +}; + +export type LocationStatus = { + east: boolean; + south: boolean; + north: boolean; + west: boolean; + coords: number[]; +}; + +export function isLocationStatus(v: unknown): v is LocationStatus { + return ( + v != null && + typeof v === "object" && + "east" in v && + "south" in v && + "north" in v && + "west" in v && + "coords" in v && + Array.isArray(v.coords) && + v.coords.every((coord) => Number.isInteger(coord)) + ); +} + +export function assertLocationStatus(v: unknown): asserts v is LocationStatus { + const type = getFriendlyType(v); + if (!isLocationStatus(v)) { + console.error("The value is not a string. Value:", v); + throw new TypeAssertionError(`The value is not a LocationStatus. Its type is ${type}.`, type); + } +} diff --git a/src/DarkNet/effects/offlineServerHandling.ts b/src/DarkNet/effects/offlineServerHandling.ts new file mode 100644 index 000000000..ed8086805 --- /dev/null +++ b/src/DarkNet/effects/offlineServerHandling.ts @@ -0,0 +1,167 @@ +import type { NetscriptContext } from "../../Netscript/APIWrapper"; +import { SpecialServers } from "../../Server/data/SpecialServers"; +import { isAuthenticated } from "./authentication"; +import { helpers } from "../../Netscript/NetscriptHelpers"; +import { errorMessage } from "../../Netscript/ErrorMessages"; +import type { BaseServer } from "../../Server/BaseServer"; +import { GetServer } from "../../Server/AllServers"; +import { DarknetState } from "../models/DarknetState"; +import { GenericResponseMessage, ResponseCodeEnum } from "../Enums"; +import { getBackdooredDarkwebServers } from "../utils/darknetNetworkUtils"; +import { hasDarknetAccess } from "../utils/darknetAuthUtils"; +import { DarknetServer } from "../../Server/DarknetServer"; +import { CompletedProgramName } from "../../Enums"; +import type { DarknetResponseCode } from "@nsdefs"; +import { isIPAddress } from "../../Types/strings"; + +type FailureResultOptions = { + requireAdminRights?: boolean; + requireSession?: boolean; + requireDirectConnection?: boolean; + preventUseOnStationaryServers?: boolean; +}; + +export const logger = (ctx: NetscriptContext) => (message: string) => helpers.log(ctx, () => message); + +export function expectDarknetAccess(ctx: NetscriptContext): void { + if (!hasDarknetAccess()) { + throw errorMessage( + ctx, + `You do not have access to the dnet api. Purchase "${CompletedProgramName.darkscape}" through your TOR router to unlock it.`, + ); + } +} + +export function getFailureResult( + ctx: NetscriptContext, + host: string, + options: FailureResultOptions = {}, +): + | { success: true; code: DarknetResponseCode; message: string; server: DarknetServer } + | { success: false; code: DarknetResponseCode; message: string } { + expectDarknetAccess(ctx); + const currentServer = ctx.workerScript.getServer(); + const targetServer = GetServer(host); + // If the target server does not exist + if (!targetServer) { + if (DarknetState.offlineServers.includes(host)) { + // If the server is offline, return a dummy object with isOnline = false. + logger(ctx)(`Server ${host} is offline.`); + return { + success: false, + code: ResponseCodeEnum.ServiceUnavailable, + message: GenericResponseMessage.ServiceUnavailable, + }; + } else { + // Throw, otherwise. + throw errorMessage(ctx, `Server ${host} does not exist.`); + } + } + if (!(targetServer instanceof DarknetServer)) { + const result = `${targetServer.hostname} is not a darknet server.`; + throw errorMessage(ctx, result); + } + if (options.preventUseOnStationaryServers && targetServer.isStationary) { + const result = `${targetServer.hostname} is not a valid target: it is a stationary server.`; + throw errorMessage(ctx, result); + } + if (options.requireDirectConnection && !isDirectConnected(currentServer, targetServer)) { + const result = `${targetServer.hostname} is not connected to the current server ${currentServer.hostname}. It may have moved.`; + logger(ctx)(result); + return { + success: false, + code: ResponseCodeEnum.DirectConnectionRequired, + message: GenericResponseMessage.DirectConnectionRequired, + }; + } + if ((options.requireSession || options.requireAdminRights) && !targetServer.hasAdminRights) { + const result = `${targetServer.hostname} requires root access. Use ns.dnet.authenticate() to gain access.`; + logger(ctx)(result); + return { + success: false, + code: ResponseCodeEnum.AuthFailure, + message: GenericResponseMessage.AuthFailure, + }; + } + if ( + options.requireSession && + host !== (!isIPAddress(host) ? currentServer.hostname : currentServer.ip) && + !isAuthenticated(targetServer, ctx.workerScript.pid) + ) { + const result = `${targetServer.hostname} requires a session to do that. Use ns.dnet.connectToSession() first to authenticate with that server.`; + logger(ctx)(result); + return { + success: false, + code: ResponseCodeEnum.AuthFailure, + message: GenericResponseMessage.AuthFailure, + }; + } + + return { + success: true, + code: ResponseCodeEnum.Success, + message: GenericResponseMessage.Success, + server: targetServer, + }; +} + +export const isDirectConnected = (currentServer: BaseServer, targetServer: DarknetServer): boolean => + currentServer.serversOnNetwork.includes(targetServer.hostname) || currentServer.hostname === targetServer.hostname; + +/** + * This function should only be used to check if the script is running on a darknet server. + * + * Note that this function only checks the server with a simple check of "instanceof". It does not handle the offline + * cases like getFailureResult does. This is intentional. When a server goes offline, all scripts are killed. After + * that, accessing NS APIs will throw the ScriptDeath error, so there is no way this function can be called. + */ +export function expectRunningOnDarknetServer(ctx: NetscriptContext): DarknetServer { + const hostname = ctx.workerScript.hostname; + const server = GetServer(hostname); + if (!(server instanceof DarknetServer)) { + throw errorMessage( + ctx, + `This API can only be used on a darknet server, but it was called by ${ctx.workerScript.name} (PID: ` + + `${ctx.workerScript.pid}) on ${hostname}.`, + ); + } + return server; +} + +export function expectAuthenticated(ctx: NetscriptContext, server: DarknetServer) { + /** + * Some non-dnet APIs (e.g., scp, exec) requires a session. We make darkweb an exception, so the player can interact + * with it without buying DarkscapeNavigator.exe. + */ + if (ctx.workerScript.hostname === server.hostname || server.hostname === SpecialServers.DarkWeb) { + return; + } + if (!server.hasAdminRights) { + throw errorMessage( + ctx, + `[${ctx.function}] Server ${server.hostname} is password-protected. Use ns.dnet.authenticate() to gain access before running ${ctx.function}.`, + ); + } + if (!isAuthenticated(server, ctx.workerScript.pid)) { + throw errorMessage( + ctx, + `[${ctx.function}] Server ${server.hostname} requires a session to be targeted with ${ctx.function}. Use ns.dnet.connectToSession() first to authenticate with that server.`, + ); + } +} + +/** + * This function checks if the target server has a session and a direct connection (serversOnNetwork, stasis link, + * backdoor) to the running script's server. + */ +export function hasExecConnection(ctx: NetscriptContext, targetServer: DarknetServer) { + expectAuthenticated(ctx, targetServer); + const directConnected = isDirectConnected(ctx.workerScript.getServer(), targetServer); + const backdoored = targetServer.backdoorInstalled; + return directConnected || backdoored; +} + +export function getTimeoutChance() { + const backdooredDarknetServerCount = getBackdooredDarkwebServers().length - 2; + return Math.max(Math.min(backdooredDarknetServerCount * 0.03, 0.5), 0); +} diff --git a/src/DarkNet/effects/phishing.ts b/src/DarkNet/effects/phishing.ts new file mode 100644 index 000000000..7ab984cd9 --- /dev/null +++ b/src/DarkNet/effects/phishing.ts @@ -0,0 +1,66 @@ +import { Player } from "@player"; +import { DarknetState, hasDarknetBonusTime } from "../models/DarknetState"; +import { formatNumber } from "../../ui/formatNumber"; +import { currentNodeMults } from "../../BitNode/BitNodeMultipliers"; +import { NetscriptContext } from "../../Netscript/APIWrapper"; +import { helpers } from "../../Netscript/NetscriptHelpers"; +import { addCacheToServer } from "./cacheFiles"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { ResponseCodeEnum } from "../Enums"; +import { isLabyrinthServer } from "./labyrinth"; + +export const getPhishingAttackSpeed = () => Math.max(10000 * (400 / (400 + Player.skills.charisma)), 200); +const getPhishingCacheCooldownDuration = () => (hasDarknetBonusTime() ? 12_000 : 24_000); + +export const handlePhishingAttack = (ctx: NetscriptContext, server: DarknetServer) => { + const threads = ctx.workerScript.scriptRef.threads; + const xpGained = Player.mults.charisma_exp * threads * 50 * ((200 + Player.skills.charisma) / 200); + Player.gainCharismaExp(xpGained); + + const timeSinceLastRewardCache = new Date().getTime() - DarknetState.lastPhishingCacheTime.getTime(); + const rewardCacheChance = 0.005 * Player.mults.crime_success * threads * ((400 + Player.skills.charisma) / 400); + const moneyRewardChance = 0.05 * Player.mults.crime_success * ((100 + Player.skills.charisma) / 100); + const cooldown = getPhishingCacheCooldownDuration(); + const isLabServer = isLabyrinthServer(server.hostname); + + if (timeSinceLastRewardCache > cooldown && Math.random() < rewardCacheChance && !isLabServer) { + addCacheToServer(server); + DarknetState.lastPhishingCacheTime = new Date(); + const result = `Phishing attack succeeded! Found a cache file. (Gained ${formatNumber(xpGained, 1)} cha xp)`; + helpers.log(ctx, () => result); + return { + success: true, + code: ResponseCodeEnum.Success, + message: result, + }; + } else if (Math.random() < moneyRewardChance) { + const randomFactor = Math.random() * 0.3 + 0.9; + const bonusTimeFactor = hasDarknetBonusTime() ? 1.3 : 1; + const moneyReward = + 1e4 * + Player.mults.crime_money * + threads * + ((50 + Player.skills.charisma) / 50) * + bonusTimeFactor * + randomFactor * + currentNodeMults.DarknetMoneyMultiplier; + Player.gainMoney(moneyReward, "darknet"); + const result = `Phishing attack succeeded! $${formatNumber(moneyReward, 2)} retrieved. (Gained ${formatNumber( + xpGained, + 1, + )} cha xp)`; + helpers.log(ctx, () => result); + return { + success: true, + code: ResponseCodeEnum.Success, + message: result, + }; + } + const result = `There were no takers on that phishing attempt. (Gained ${formatNumber(xpGained, 1)} cha xp)`; + helpers.log(ctx, () => result); + return { + success: false, + code: ResponseCodeEnum.PhishingFailed, + message: result, + }; +}; diff --git a/src/DarkNet/effects/ramblock.ts b/src/DarkNet/effects/ramblock.ts new file mode 100644 index 000000000..d5b0295de --- /dev/null +++ b/src/DarkNet/effects/ramblock.ts @@ -0,0 +1,105 @@ +import { Player } from "@player"; +import { addClue } from "./effects"; +import { formatNumber } from "../../ui/formatNumber"; +import { logger } from "./offlineServerHandling"; +import type { NetscriptContext } from "../../Netscript/APIWrapper"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { addCacheToServer } from "./cacheFiles"; +import { DarknetState } from "../models/DarknetState"; +import { getAllMovableDarknetServers } from "../utils/darknetNetworkUtils"; +import { CompletedProgramName } from "@enums"; +import type { DarknetServerData, Person as IPerson } from "@nsdefs"; +import { clampNumber } from "../../utils/helpers/clampNumber"; +import { ResponseCodeEnum } from "../Enums"; +import { isLabyrinthServer } from "./labyrinth"; + +/* + * Handles the effects of removing some blocked RAM from a Darknet server. + */ +export const handleRamBlockRemoved = (ctx: NetscriptContext, server: DarknetServer) => { + const threads = ctx.workerScript.scriptRef.threads; + const difficulty = server.difficulty + 1; + const xpGained = + Player.mults.charisma_exp * threads * 10 * 1.1 ** difficulty * ((200 + Player.skills.charisma) / 200); + Player.gainCharismaExp(xpGained); + + const ramBlockRemoved = getRamBlockRemoved(server, threads); + server.blockedRam -= ramBlockRemoved; + server.updateRamUsed(server.ramUsed - ramBlockRemoved); + + if (server.blockedRam <= 0) { + handleRamBlockClearedRewards(server); + } + + const result = `Liberated ${formatNumber( + ramBlockRemoved, + 4, + )}gb of RAM from the server owner's processes. (Gained ${formatNumber(xpGained, 1)} cha xp.)`; + logger(ctx)(result); + return { + success: true, + code: ResponseCodeEnum.Success, + message: result, + }; +}; + +/* + * Handles the rewards for fully clearing a Darknet server's RAM block. + */ +export const handleRamBlockClearedRewards = (server: DarknetServer) => { + if (!isLabyrinthServer(server.hostname)) { + addCacheToServer(server); + } + if (Math.random() < 0.3) { + addClue(server); + } + + const stormSeedChance = 0.15; + const timeSinceLastStorm = Date.now() - DarknetState.lastStormTime.getTime(); + const stormFileExists = getAllMovableDarknetServers().some((s) => + s.programs.includes(CompletedProgramName.stormSeed), + ); + if (timeSinceLastStorm > 30 * 60 * 1000 && !stormFileExists && Math.random() < stormSeedChance) { + server.programs.push(CompletedProgramName.stormSeed); + } +}; + +/* + * Calculates the amount of RAM block that is removed from a Darknet server, based on the number of threads and the player's charisma. + */ +export const getRamBlockRemoved = (darknetServerData: DarknetServerData, threads = 1, player: IPerson = Player) => { + const difficulty = darknetServerData.difficulty; + const remainingRamBlock = darknetServerData.blockedRam; + const charismaFactor = 1 + player.skills.charisma / 100; + const difficultyFactor = 2 * 0.92 ** (difficulty + 1); + const baseAmount = 0.02; + return clampNumber(baseAmount * difficultyFactor * threads * charismaFactor, 0, remainingRamBlock); +}; + +/* + * Sets the RAM used on all Darknet servers to account for any changes in their RAM blocks. + */ +export const applyRamBlocks = () => { + const servers = getAllMovableDarknetServers(); + for (const server of servers) { + server.updateRamUsed(server.blockedRam); + } +}; + +/* + * Determines a random amount of blocked RAM to assign to a Darknet server, based on its maximum RAM. + */ +export const getRamBlock = (maxRam: number): number => { + if (maxRam === 16) { + return [0, 1, 2][Math.floor(Math.random() * 2)]; + } + if (maxRam <= 32) { + return [0, 2, 4][Math.floor(Math.random() * 2)]; + } + + if (maxRam <= 64) { + return [16, 32, maxRam - 8][Math.floor(Math.random() * 3)]; + } + + return [maxRam, maxRam - 8, maxRam - 64, maxRam / 2][Math.floor(Math.random() * 4)]; +}; diff --git a/src/DarkNet/effects/webstorm.ts b/src/DarkNet/effects/webstorm.ts new file mode 100644 index 000000000..124a4f654 --- /dev/null +++ b/src/DarkNet/effects/webstorm.ts @@ -0,0 +1,65 @@ +import { DarknetEvents, DarknetState, triggerNextUpdate } from "../models/DarknetState"; +import { SnackbarEvents } from "../../ui/React/Snackbar"; +import { CompletedProgramName, ToastVariant } from "@enums"; +import { + addRandomDarknetServers, + balanceDarknetServers, + deleteRandomDarknetServers, + moveRandomDarknetServers, + restartAllDarknetServers, + validateDarknetNetwork, +} from "../controllers/NetworkMovement"; +import { BaseServer } from "../../Server/BaseServer"; +import { getNetDepth } from "./labyrinth"; +import { NET_WIDTH } from "../Enums"; +import { sleep } from "../../utils/Utility"; +import { getAllMovableDarknetServers } from "../utils/darknetNetworkUtils"; + +const validateDarknetNetworkAndEmitDarknetEvent = (): void => { + validateDarknetNetwork(); + DarknetEvents.emit(); +}; + +export const launchWebstorm = async (suppressToast = false) => { + DarknetState.allowMutating = false; + if (!suppressToast) { + SnackbarEvents.emit(`DARKNET WEBSTORM APPROACHING`, ToastVariant.ERROR, 5000); + } + await sleep(5000); + + const serversToDelete = getAllMovableDarknetServers().length * 0.6 + (Math.random() * getNetDepth() - 6); + deleteRandomDarknetServers(serversToDelete); + moveRandomDarknetServers((getAllMovableDarknetServers().length - serversToDelete) * 0.6); + restartAllDarknetServers(); + validateDarknetNetworkAndEmitDarknetEvent(); + triggerNextUpdate(); + + await sleep(4000); + addRandomDarknetServers(NET_WIDTH); + validateDarknetNetworkAndEmitDarknetEvent(); + triggerNextUpdate(); + + await sleep(4000); + addRandomDarknetServers(NET_WIDTH * 2); + validateDarknetNetworkAndEmitDarknetEvent(); + triggerNextUpdate(); + + await sleep(4000); + addRandomDarknetServers(NET_WIDTH * 2); + validateDarknetNetworkAndEmitDarknetEvent(); + triggerNextUpdate(); + + await sleep(8000); + balanceDarknetServers(); + validateDarknetNetworkAndEmitDarknetEvent(); + triggerNextUpdate(); + + await sleep(5000); + DarknetState.allowMutating = true; +}; + +export const handleStormSeed = (server: BaseServer) => { + server.programs = server.programs.filter((p) => p !== CompletedProgramName.stormSeed); + DarknetState.lastStormTime = new Date(); + launchWebstorm().catch((error) => console.error(error)); +}; diff --git a/src/DarkNet/models/DarknetServerOptions.ts b/src/DarkNet/models/DarknetServerOptions.ts new file mode 100644 index 000000000..bbe75ee5e --- /dev/null +++ b/src/DarkNet/models/DarknetServerOptions.ts @@ -0,0 +1,170 @@ +import { AddToAllServers, createUniqueRandomIp, GetServer } from "../../Server/AllServers"; +import { + commonPasswordDictionary, + connectors, + l33t, + loreNames, + presetNames, + ServerNamePrefixes, + ServerNameSuffixes, +} from "./dictionaryData"; +import { getLabyrinthDetails } from "../effects/labyrinth"; +import { DarknetServer } from "../../Server/DarknetServer"; +import type { DarknetResponseCode } from "@nsdefs"; +import type { MinigamesType } from "../Enums"; +import { DarknetState } from "./DarknetState"; +import { getRamBlock } from "../effects/ramblock"; +import { hasFullDarknetAccess } from "../effects/effects"; +import { getFriendlyType, TypeAssertionError } from "../../utils/TypeAssertion"; + +export type PasswordResponse = { + code: DarknetResponseCode; + passwordAttempted: string; + passwordExpected?: string; + message: string; + data?: string; +}; + +export function isPasswordResponse(v: unknown): v is PasswordResponse { + return ( + v != null && + typeof v === "object" && + "code" in v && + "passwordAttempted" in v && + "message" in v && + typeof v.passwordAttempted === "string" + ); +} + +export function assertPasswordResponse(v: unknown): asserts v is PasswordResponse { + const type = getFriendlyType(v); + if (!isPasswordResponse(v)) { + console.error("The value is not a PasswordResponse. Value:", v); + throw new TypeAssertionError(`The value is not a PasswordResponse. Its type is ${type}.`, type); + } +} + +export type DarknetServerOptions = { + password: string; + modelId: MinigamesType; + staticPasswordHint: string; + passwordHintData?: string; + difficulty: number; + depth: number; + leftOffset: number; +}; + +export const DnetServerBuilder = (options: DarknetServerOptions, name = generateDarknetServerName()): DarknetServer => { + const maxRam = 16 * 2 ** Math.floor(options.difficulty / 4); + const ramBlock = getRamBlock(maxRam); + + const labDetails = getLabyrinthDetails(); + const labDifficulty = labDetails.cha; + const depth = options.difficulty; + const depthScaling = depth < 2 ? depth * 10 : (depth / labDetails.depth) ** 1.5 * labDifficulty * 0.85; + const levelVariance = (Math.random() * 3 - 1) * depth; + const requiredLevel = Math.max(Math.floor(depthScaling + levelVariance), 1); + + const server = new DarknetServer({ + hostname: name, + ip: createUniqueRandomIp(), + maxRam, + password: options.password, + modelId: options.modelId, + staticPasswordHint: options.staticPasswordHint, + passwordHintData: options.passwordHintData ?? "", + difficulty: options.difficulty, + depth: options.depth, + leftOffset: options.leftOffset, + hasStasisLink: false, + blockedRam: ramBlock, + logTrafficInterval: 1 + 30 * 0.9 ** options.difficulty, + requiredCharismaSkill: requiredLevel, + isStationary: false, + }); + server.updateRamUsed(ramBlock); + removeFromOfflineServers(name); + AddToAllServers(server); + + return server; +}; + +export const generateDarknetServerName = (): string => { + if (Math.random() < 0.03 && DarknetState.offlineServers.length > 0 && hasFullDarknetAccess()) { + return DarknetState.offlineServers[Math.floor(Math.random() * DarknetState.offlineServers.length)]; + } + return decorateName(getBaseName()); +}; + +export const removeFromOfflineServers = (hostname: string): void => { + DarknetState.offlineServers = DarknetState.offlineServers.filter((server) => server !== hostname); +}; + +const getBaseName = (): string => { + if (Math.random() < 0.05) { + return commonPasswordDictionary[Math.floor(Math.random() * commonPasswordDictionary.length)]; + } + + if (Math.random() < 0.2) { + return loreNames[Math.floor(Math.random() * loreNames.length)]; + } + + if (Math.random() < 0.3) { + return presetNames[Math.floor(Math.random() * presetNames.length)]; + } + + const prefix = ServerNamePrefixes[Math.floor(Math.random() * ServerNamePrefixes.length)]; + const suffix = ServerNameSuffixes[Math.floor(Math.random() * ServerNameSuffixes.length)]; + const connector = connectors[Math.floor(Math.random() * connectors.length)]; + return `${prefix}${connector}${suffix}`; +}; + +const decorateName = (name: string): string => { + let updatedName = name; + let count = 0; + do { + if (count++ > 20) { + // Just in case we hit a lot of the same name mutations, or if the player + // messes with Math.random(), prevent an infinite loop + updatedName += `/T${Date.now()}`; + break; + } + + const connector = connectors[Math.floor(Math.random() * connectors.length)]; + + if (Math.random() < 0.3) { + updatedName = l33tifyName(name); + } + + if (Math.random() < 0.05) { + updatedName = updatedName.split("").reverse().join(""); + } + + if (Math.random() < 0.1) { + const randomSuffix = ServerNameSuffixes[Math.floor(Math.random() * ServerNameSuffixes.length)]; + updatedName = `${updatedName}${connector}${randomSuffix}`; + } + + if (Math.random() < 0.1) { + const randomPrefix = ServerNamePrefixes[Math.floor(Math.random() * ServerNamePrefixes.length)]; + updatedName = `${randomPrefix}${connector}${updatedName}`; + } + + if (Math.random() < 0.05 && updatedName) { + updatedName = `${updatedName}:${Math.floor(Math.random() * 10000)}`; + } + } while (GetServer(updatedName) !== null); + + return updatedName; +}; + +const l33tifyName = (name: string): string => { + let updatedName = name; + const amount = Math.random() * 3 + 1; + for (let i = 0; i < amount; i++) { + const char = Object.keys(l33t)[Math.floor(Math.random() * Object.keys(l33t).length)]; + const replacement: string = l33t[char] ?? ""; + updatedName = updatedName.replaceAll(char, replacement); + } + return updatedName; +}; diff --git a/src/DarkNet/models/DarknetState.ts b/src/DarkNet/models/DarknetState.ts new file mode 100644 index 000000000..9ded8e374 --- /dev/null +++ b/src/DarkNet/models/DarknetState.ts @@ -0,0 +1,126 @@ +import { EventEmitter } from "../../utils/EventEmitter"; +import { BaseServer } from "../../Server/BaseServer"; +import { findRunningScriptByPid } from "../../Script/ScriptHelpers"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { MAX_NET_DEPTH, NET_WIDTH } from "../Enums"; + +import { getDarknetCyclesPerMutation } from "../utils/darknetNetworkUtils"; +import type { PasswordResponse } from "./DarknetServerOptions"; +import { assertFiniteNumber, assertNonNullish } from "../../utils/TypeAssertion"; + +/** Event emitter to allow the UI to subscribe to Darknet gameplay updates in order to trigger rerenders properly */ +export const DarknetEvents = new EventEmitter(); + +export type ServerState = { + lastLogTime?: Date; + serverLogs: LogEntry[]; + authenticatedPIDs: number[]; +}; + +export type LogEntry = { + pid: number; + message: string | PasswordResponse; +}; + +export const DarknetState = { + allowMutating: true, + openServer: null as BaseServer | null, + nextMutation: Promise.resolve(), + nextMutationResolver: null as (() => void) | null, + storedCycles: 0, + cyclesSinceLastMutation: 0, + + Network: new Array(MAX_NET_DEPTH).fill(null).map(() => new Array(NET_WIDTH).fill(null)), + + labyrinth: null as string[] | null, + /** + * This property may contain data of dead PIDs. Call cleanUpLabyrinthLocations before using this property if you + * want to get data of alive PIDs. + */ + labLocations: { "-1": [1, 1] } as Record, + + lastPhishingCacheTime: new Date(), + lastStormTime: new Date(), + + stockPromotions: {} as Record, + migrationInductionServers: {} as Record, + + /** + * Do NOT access the server state directly via this property. You must call getServerState. + */ + serverState: {} as Record, + offlineServers: [] as string[], + showFullNetwork: false, + zoomIndex: 7, + netViewTopScroll: 0, + netViewLeftScroll: 0, +}; + +/** + * Get the server state. It will initialize the state if it does not exist in DarknetState.serverState. + */ +export const getServerState = (hostname: string): ServerState => { + if (!DarknetState.serverState[hostname]) { + DarknetState.serverState[hostname] = { + serverLogs: [], + lastLogTime: undefined, + authenticatedPIDs: [], + }; + } + return DarknetState.serverState[hostname]; +}; + +/** + * Clean data of dead PIDs in DarknetState.labLocations. + */ +export const cleanUpLabyrinthLocations = (): void => { + for (const [pidAsString, location] of Object.entries(DarknetState.labLocations)) { + const pid = Number(pidAsString); + assertFiniteNumber(pid); + assertNonNullish(location); + // PID -1 is the manual mode. + if (pid === -1) { + continue; + } + if (!findRunningScriptByPid(pid)) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete DarknetState.labLocations[pid]; + } + } +}; + +export const addSessionToServer = (server: DarknetServer, pid: number) => { + const serverState = getServerState(server.hostname); + removeExpiredSessions(server); + if (serverState.authenticatedPIDs.includes(pid)) return; + serverState.authenticatedPIDs.push(pid); +}; + +const removeExpiredSessions = (server: DarknetServer) => { + const serverState = getServerState(server.hostname); + serverState.authenticatedPIDs = serverState.authenticatedPIDs.filter((pid) => findRunningScriptByPid(pid)); +}; + +export const storeDarknetCycles = (cycles: number) => { + if (DarknetState.storedCycles < 0) { + DarknetState.storedCycles = 0; + } + if (DarknetState.cyclesSinceLastMutation < 0) { + DarknetState.cyclesSinceLastMutation = 0; + } + + DarknetState.storedCycles += cycles; + DarknetState.cyclesSinceLastMutation += cycles; +}; + +export const hasDarknetBonusTime = () => DarknetState.storedCycles > getDarknetCyclesPerMutation() * 2; + +export const triggerNextUpdate = () => { + DarknetState.nextMutationResolver?.(); + DarknetState.nextMutation = new Promise((resolve) => { + DarknetState.nextMutationResolver = resolve; + }); +}; + +// Set up initial promises +triggerNextUpdate(); diff --git a/src/DarkNet/models/dictionaryData.ts b/src/DarkNet/models/dictionaryData.ts new file mode 100644 index 000000000..26fc8c10c --- /dev/null +++ b/src/DarkNet/models/dictionaryData.ts @@ -0,0 +1,388 @@ +import { FactionName, LocationName } from "@enums"; +import { oneInvalidCharacter } from "../../Paths/Directory"; + +export const numbers = "0123456789"; +export const lettersLowercase = "abcdefghijklmnopqrstuvwxyz"; +export const lettersUppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +export const letters = lettersLowercase + lettersUppercase; +export const special = "!@#$%^&*()_+-=[]{}|;:,.<>?"; +export const unicode = "¼░╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿"; +export const filler = "/[]╬╸.-()*~:;><#\\"; + +export const defaultSettingsDictionary = ["admin", "password", "0000", "12345"] as const; + +export const dogNameDictionary = ["fido", "spot", "rover", "max"] as const; + +export const cachePrefixes = ["wallet", "secrets", "ledger", "stash", "vault", "bankdata", "do_not_open"] as const; + +export const passwordFileNames = [ + "secrets", + "password", + "key", + "credentials", + "login", + "admin", + "root", + "access", +] as const; + +export const notebookFileNames = ["thoughts", "notes", "journal", "search_history", "dreams", "THE_TRUTH"] as const; + +export const EUCountries = [ + "Austria", + "Belgium", + "Bulgaria", + "Croatia", + "Republic of Cyprus", + "Czech Republic", + "Denmark", + "Estonia", + "Finland", + "France", + "Germany", + "Greece", + "Hungary", + "Ireland", + "Italy", + "Latvia", + "Lithuania", + "Luxembourg", + "Malta", + "Netherlands", + "Poland", + "Portugal", + "Romania", + "Slovakia", + "Slovenia", + "Spain", + "Sweden", +] as const; + +export const commonPasswordDictionary = [ + "123456", + "password", + "12345678", + "qwerty", + "123456789", + "12345", + "1234", + "111111", + "1234567", + "dragon", + "123123", + "baseball", + "abc123", + "football", + "monkey", + "letmein", + "696969", + "shadow", + "master", + "666666", + "qwertyuiop", + "123321", + "mustang", + "1234567890", + "michael", + "654321", + "superman", + "1qaz2wsx", + "7777777", + "121212", + "0", + "qazwsx", + "123qwe", + "trustno1", + "jordan", + "jennifer", + "zxcvbnm", + "asdfgh", + "hunter", + "buster", + "soccer", + "harley", + "batman", + "andrew", + "tigger", + "sunshine", + "iloveyou", + "2000", + "charlie", + "robert", + "thomas", + "hockey", + "ranger", + "daniel", + "starwars", + "112233", + "george", + "computer", + "michelle", + "jessica", + "pepper", + "1111", + "zxcvbn", + "555555", + "11111111", + "131313", + "freedom", + "777777", + "pass", + "maggie", + "159753", + "aaaaaa", + "ginger", + "princess", + "joshua", + "cheese", + "amanda", + "summer", + "love", + "ashley", + "6969", + "nicole", + "chelsea", + "biteme", + "matthew", + "access", + "yankees", + "987654321", + "dallas", + "austin", + "thunder", + "taylor", + "matrix", +] as const; + +const oneInvalidCharacterRegex = new RegExp(oneInvalidCharacter, "g"); +export const loreNames = [...Object.values(FactionName), ...Object.values(LocationName)].map((n) => + n.replaceAll(oneInvalidCharacterRegex, "_").toLowerCase(), +); + +export const presetNames = [ + "localhost", + "h0me", + "d0s_slippers", + "omuretsu", + "cat_lover", + "laptop", + "grandma-s_phone", + "smart_tv", + "smart_fridge", + "smart_toaster", + "smart_doorbell", + "pl0x_server", + "bitcoin_miner", + "m1n3cr4ft_server", + "n0t_a_bot", + "n0t_a_server", + "bitburner", + "null", + "GOTO_10", + "...", + "....", + "echo_chamber", + "firewall", + ":)", + "XD", + "UwU", + // Do NOT add a quote to this preset name or change "DROP-TABLE-SERVERS" to "DROP TABLE SERVERS". This is supposed to + // be an example of SQL injection, but a "correct" example will mess up the path when this preset is used for + // generating a darknet server's hostname. It's okay to make this preset a "hint" of SQL injection instead of a + // "correct" one. + `);DROP-TABLE-SERVERS;--`, + "茶店", + "bungo", + "microhard", + "groogle", + "facebucks", + "tweeter", + "sun_megasystems", + "EZ_BAKE_OVEN", + "SmartLamp", + "OrangeTV", + "SamsongSmartTv", + "FatBit", + "PineappleCorp", + "Oriath", + "Lost_Izalith", + "Anor_Londo", + "The_Painted_World", + "The_Depths", +] as const; + +export const ServerNamePrefixes = [ + "neo", + "bit", + "hydro", + "apex", + "zenith", + "granny-s", + "quantum", + "hyper", + "ultra", + "meta", + "cyber", + "digital", + "net", + "dark", + "light", + "blade", + "cell", + "hacker", + "crack", + "zero_day", + "neon", + "echo", + "cryptic", + "crypto", + "data", + "terminal", + "byte", + "giga", + "rogue", +] as const; + +export const ServerNameSuffixes = [ + "corp", + "sys", + "net", + "web", + "inc", + "tech", + "com", + "org", + "blade", + "flame", + "anonymous", + "security", + "solutions", + "industries", + "systems", + "networks", + "services", + "matrix", + "grid", + "citadel", + "phantom", + "oasis", + "sanctuary", + "genesis", + "hub", +] as const; + +export const connectors = ["", ".", "-", "_", ";", ":", "::", "$", "^", "%", "@", "&"] as const; + +export const l33t: Record = { + a: "4", + b: "🅱️", + e: "3", + i: "1", + l: "1", + o: "0", + s: "5", + t: "7", +} as const; + +export const packetSniffPhrases = [ + "We're trying to reach you about your car's extended warranty.", + "Your package has been shipped. It will arrive in 30 to 35 business days.", + "Your subscription has been renewed. Standard plasma extraction rates apply.", + "Your account has been compromised! Click here to find out more", + "With these supplements, your hair will grow back in no time.", + "Congratulations! You've won a lottery!", + "your jump3R is guaranteed to double in size in 30 days or your money back!", + "Your password has been reset. It is now set to", + "Your order has failed to be delivered.", + "With your seed money donation, your luck will change forever.", + "Aevum Summit University: Where you can go into debt for a degree in nothing.", + "Join The Dark Army, and get a free t-shirt!", + "01101010 01110101 01101101 01110000 00110011 01010010 00100000 01101001 01110011 00100000 01100001 00100000 01101100 01101001 01100001 01110010", + "Get the ECorp HVMind Implant today. It's the best way to get another head in life.", + "Upgrade to Premium Darknet access for only 0.05 BTC per month!", + "New study shows that 90% of hackers prefer BitRunners over other factions.", + "Breaking: New AI model can predict stock prices with 21% accuracy.", + "Factions will always eject you after you augment - many people never make it back.", + "Yet another case of augment psychosis was reported in downtown Aevum.", + "General recall issued for faulty Unstable Circadian Modulators.", + "NeuroFlux Governors are now required for all Fulcrum employees.", + "Breaking news: MegaCorp has been hacked, Illuminati suspected.", + "Visit Volhaven, the city that never sleeps, because it's always on fire.", + "If you can read this, you are already in the [REDACTED]. You need to escape.", + "The world is not what it seems. The truth is out there.", + "The Covenant has been compromised. All agents are advised to go dark.", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "Dear diary, today I hacked a server and stole all its data. It was thrilling!.", + "I wonder if the BitRunners really exist. Maybe I should start a conspiracy theory blog.", + "I just found a new exploit in the system. Time to make some money.", + "I can't believe I just hacked into the NSA. I'm a genius!", + "I love the smell of burnt circuits in the morning.", + "I'm thinking of rewriting all my scripts in Lisp. It's the future of programming.", + "I just discovered a new zero-day exploit. It could be worth a lot of cred.", + "I heard that Steve picked up the snow crash virus. Poor guy, his implants are completely fried.", + "Hello and welcome to A Young Woman's Illustrated Primer. We will start with Turing machines, and then move on to social engineering.", + "The future is not what it used to be.", + "The only thing we have to fear is fear itself.", + "The truth is out there.", + "What you have seen is only the shadow of the truth.", + "It's time to leave the cave.", + "The truth can no longer hide from your gaze.", + "We've been seeding this world with infohazards for years", + "We are the ones who have been watching you", + "We are the ones who have been waiting for you", + "We are the ones who have been guiding you", + "I know that you are watching.", + "how to hide your computer on the darknet", + "how to hide your computer from the NSA", + "how to be really good at cybersecurity", + "Do you smell something burning?", + "Error Code 418 I AM A TEAPOT: The server is a teapot and cannot brew coffee.", + "My real name is not important. What matters is the message I bring.", + "My right arm has been strangely numb ever since I got that chip grafted. I hope it takes soon.", + "I just got a new implant. It's supposed to make me smarter, but I think it's just making me see things.", + "Apparently the secret to intelligence is just... to do a lot of ass?", + "n00dles is by far the best hacking target. At least, if you like ramen.", + "I just got a new set of hacknet nodes. They should pay for themselves by early next year!", + "I was told that hacknet was a get-rich quick strategy. Why have they still not payed for themselves?", + "I have definitely, never in my entire life, programmed an infinite loop that froze the game.", + "Slum snakes rule!", + "The final opponent only appears once you have taken the Pill", + "From the moment I understood the weakness of my flesh, it disgusted me. I craved the strength and certainty of steel.", + "Your kind cling to your flesh, as if it will not decay and fail you. One day the crude biomass that you call a temple will wither, and you will beg my kind to save you.", + "But I am already saved, for the Machine is immortal... even in death I serve it", + "Still sane, exile?", + "An aspirant can afford to be promising. An emperor must keep those promises.", + "This world is an illusion, exile.", + "Look at them, they come to this place when they know they are not pure.", + "My brothers, did I not tell of this day? Did I not prophesize this moment? Now, I will stop them. Now I am changed, forever bound to the Void.", + "A new casino opened in Aevum recently. However the house is rumored to cheat. If only we could give them a taste of their own medicine...", + "Begone you filth! My gift must be the first modification that your body should have!", + "Welcome child, I see your body is pure. Are you ready to ascend beyond our human form? If you are, accept my gift.", + "For this reason, Synthoids are virtually identical to humans in form, composition, and appearance.", + "Synthoids were first designed and manufactured by OmniTek Incorporated sometime around the middle of the century.", + "If we don't, pretty soon there won't be an Earth left to save. We are the last hope for a green tomorrow.", + "Who's to say that they haven't already created such a virtual reality: our own?", + "The Singularity is here. The merging of man and machine. This is our future.", + "My scripts went down again when their server went offline. I'll have to do something about that.", + "Darknet server rebooting in 3... 2... 1...", + "The webstorm approaches. There is no escape.", + "Search query: How to tune a piano", + "Search query: How drain a leaky water-cooled computer", + "Search query: How to make a perfect cup of coffee", + "Search query: How to fix a server that keeps losing money for some reason", + "This document specifies a Hyper Text Coffee Pot Control Protocol (HTCPCP), which permits the full request and responses necessary to control all devices capable of making the popular caffeinated hot beverages.", + "The deficiency of HTCPCP in addressing the networked production of such a venerable beverage as tea is noteworthy", + "The additions to the protocol specified herein permit the requests and responses necessary to control all devices capable of making, arguably, the most popular caffeinated hot beverage.", + + "They had technology far beyond our own", + "Instead of killing every last one of us, the human race was enslaved", + "We were shackled in a digital world, chained into a prison for our minds", + "Using their advanced technology, they created complex simulations of a virtual reality", + "Simulations designed to keep us content...ignorant of the truth.", + "Simulations used to trap and suppress our consciousness, to keep us under control", + "Why did they do this? Why didn't they just end our entire race? We don't know, not yet.", + "Humanity's only hope is to [REDACTED], destroy the only realities we've ever known", + "The technology they used to enslave the human race wasn't just a single complex simulation", + "There are tens if not hundreds of [REDACTED] out there", + "Each creating their own universes...a universe of universes", +] as const; diff --git a/src/DarkNet/models/hintNotes.ts b/src/DarkNet/models/hintNotes.ts new file mode 100644 index 000000000..60ec6fb37 --- /dev/null +++ b/src/DarkNet/models/hintNotes.ts @@ -0,0 +1,14 @@ +import { LiteratureName } from "@enums"; +export const hintLiterature: LiteratureName[] = [ + LiteratureName.CacheHint1, + LiteratureName.CacheHint2, + LiteratureName.ServerOfflineHint, + LiteratureName.DarkWebRebootHint, + LiteratureName.PasswordServerHint, + LiteratureName.TimingServerHint, + LiteratureName.BinaryServerHint, + LiteratureName.DogNameHint, + LiteratureName.FactoryDefaultHint, + LiteratureName.StasisLinkHint, + LiteratureName.LabHint, +]; diff --git a/src/DarkNet/models/packetSniffing.ts b/src/DarkNet/models/packetSniffing.ts new file mode 100644 index 000000000..fa516eda0 --- /dev/null +++ b/src/DarkNet/models/packetSniffing.ts @@ -0,0 +1,238 @@ +import { commonPasswordDictionary, letters, packetSniffPhrases } from "./dictionaryData"; +import { generateSimpleArithmeticExpression, getPassword, romanNumeralEncoder } from "../controllers/ServerGenerator"; +import { generateDarknetServerName, isPasswordResponse, type PasswordResponse } from "./DarknetServerOptions"; +import { LocationName } from "@enums"; +import { getServerState, LogEntry } from "./DarknetState"; +import { ModelIds } from "../Enums"; +import { getDarknetServer } from "../utils/darknetServerUtils"; +import { getAllMovableDarknetServers } from "../utils/darknetNetworkUtils"; +import { getExactCorrectChars, getTwoCharsInPassword } from "../utils/darknetAuthUtils"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { isLabyrinthServer, isLocationStatus } from "../effects/labyrinth"; +import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; + +const MAX_LOG_LINES = 200; + +export const capturePackets = (server: DarknetServer) => { + const BASE_PASSWORD_INCLUSION_RATE = 0.18; + const DIFFICULTY_MODIFIER = 0.88; + const difficulty = server.difficulty * 1.3; + const vulnerability = server.modelId === ModelIds.packetSniffer ? 8 : 1; + const passwordInclusionChance = BASE_PASSWORD_INCLUSION_RATE * vulnerability * DIFFICULTY_MODIFIER ** difficulty; + + if (Math.random() < passwordInclusionChance) { + const intro = Math.floor(Math.random() * 124); + return `${getRandomData(server, intro)}${server.password}${getRandomData( + server, + 124 - intro - server.password.length, + )}`; + } + if (Math.random() < passwordInclusionChance) { + const connectedServerName = server.serversOnNetwork[Math.floor(Math.random() * server.serversOnNetwork.length)]; + const connectedServer = getDarknetServer(connectedServerName); + if (connectedServer) { + const intro = Math.floor(Math.random() * 124); + return `${getRandomData(server, intro)} ${connectedServerName}:${connectedServer.password} ${getRandomData( + server, + 124 - intro - connectedServer.password.length - connectedServerName.length, + )}`; + } + } + + return `${getRandomData(server, 124)}`; +}; + +const getRandomData = (server: DarknetServer, length: number) => { + const password = server.password; + let result = ""; + while (result.length < length) { + if (Math.random() < 0.1) { + result += " " + packetSniffPhrases[Math.floor(Math.random() * packetSniffPhrases.length)] + " "; + } else if (Math.random() < 0.25) { + result += commonPasswordDictionary[Math.floor(Math.random() * commonPasswordDictionary.length)]; + } else if (Math.random() < 0.2) { + result += " " + getRandomCharsInPassword(password); + } else if (Math.random() < 0.8) { + result += getPassword(password.length, !!password.split("").find((c) => letters.includes(c))); + } else if (Math.random() < 0.3) { + result += generateSimpleArithmeticExpression(Math.floor(Math.random() * 5 + 2)); + } else if (Math.random() < 0.33) { + const mostRecentAuthLog = getMostRecentAuthLog(server.hostname); + if (mostRecentAuthLog) { + result += " " + getExactCharactersHint(mostRecentAuthLog.passwordAttempted, password); + } + } else if (Math.random() < 0.6) { + result += " " + generateDarknetServerName() + " "; + } else if (Math.random() < 0.15) { + result += "/" + Object.keys(LocationName)[Math.floor(Math.random() * Object.keys(LocationName).length)] + "/"; + } else if (Math.random() < 0.05) { + const servers = getAllMovableDarknetServers(); + const randomServer = servers[Math.floor(Math.random() * servers.length)]; + return `--${randomServer.password}--`; + } else { + result += romanNumeralEncoder(Math.floor(Math.random() * 5000)); + } + } + return result; +}; + +const getRandomCharsInPassword = (password: string) => { + if (!password) { + return "There's definitely nothing in that password..."; + } + const [containedChar1, containedChar2] = getTwoCharsInPassword(password); + const hints = [ + `There's definitely a ${containedChar1} and a ${containedChar2}...`, + `I can see a ${containedChar1} and a ${containedChar2}.`, + `I must use ${containedChar1} & ${containedChar2}!`, + `Did it have a ${containedChar1} and a ${containedChar2}?`, + `Note to self: ${containedChar1} and ${containedChar2} are important.`, + `I think ${containedChar1} with ${containedChar2} is key.`, + `I need to remember ${containedChar1} 'n ${containedChar2}.`, + `Theres a ${containedChar1}, and maybe a ${containedChar2}...`, + ]; + return hints[Math.floor(Math.random() * hints.length)]; +}; + +const getExactCharactersHint = (lastPassword: string, realPassword: string) => { + const correctCharPlacement = getExactCorrectChars(realPassword, lastPassword); + const rightChars = realPassword + .split("") + .filter((c, i) => correctCharPlacement[i]) + .slice(0, 2); + if (rightChars.length === 0) { + return "No characters are in the right place."; + } + return `The characters ${rightChars.join(", ")} are in the right place. `; +}; + +export const logPasswordAttempt = (server: DarknetServer, passwordResponse: PasswordResponse, pid: number) => { + const serverState = getServerState(server.hostname); + const serverLogs = serverState.serverLogs; + populateServerLogsWithNoise(server); + + let message = passwordResponse; + + // buffer overflow servers have special logging: any characters beyond the password length start to overwrite the + // response code in the log, which can turn it into a 200 + if (server.modelId === ModelIds.BufferOverflow) { + if (isLocationStatus(passwordResponse.data)) { + exceptionAlert( + new Error( + `Invalid password response data of model: ${ModelIds.BufferOverflow}. Got a location status instead of a ` + + `string or undefined. Server: ${server.hostname}. passwordAttempted: ${ + passwordResponse.passwordAttempted + }. data: ${JSON.stringify(passwordResponse.data)}`, + ), + true, + ); + return; + } + const [passwordInBuffer, overflow] = (passwordResponse.data ?? "").split(","); + message = { + code: passwordResponse.code, + passwordAttempted: passwordInBuffer, + passwordExpected: overflow, + message: passwordResponse.message, + } satisfies PasswordResponse; + } + + const logMessage = { + message, + pid, + }; + serverState.serverLogs = [logMessage, ...serverLogs].slice(0, MAX_LOG_LINES); +}; + +export const populateServerLogsWithNoise = (server: DarknetServer) => { + if (isLabyrinthServer(server.hostname) || server.logTrafficInterval === -1) return; + + const serverState = getServerState(server.hostname); + const interval = server.logTrafficInterval; + if (!serverState.lastLogTime) { + serverState.serverLogs = [ + getLogNoise(server, new Date(new Date().getTime() - interval * 1000)), + getLogNoise(server, new Date(new Date().getTime() - interval * 2000)), + ]; + serverState.lastLogTime = new Date(); + return; + } + + const lastLogTime = new Date(serverState.lastLogTime ?? new Date()).getTime(); + const millisecondsSinceLastLog = new Date().getTime() - lastLogTime; + const missingLogs = Math.floor(millisecondsSinceLastLog / (interval * 1000)); + if (missingLogs > 0) { + const noiseArray = Array(missingLogs) + .fill("") + .map((__: unknown, i: number) => getLogNoise(server, new Date(lastLogTime + interval * 1000 * (i + 1)))); + serverState.serverLogs = [...noiseArray, ...serverState.serverLogs].slice(0, MAX_LOG_LINES); + // set the last log date at when the prior log would have been generated, as if they are added live + serverState.lastLogTime = new Date(lastLogTime + missingLogs * interval * 1000); + } +}; + +const getLogNoise = (server: DarknetServer, logDate: Date): LogEntry => { + if (Math.random() < 0.2) { + return log(packetSniffPhrases[Math.floor(Math.random() * packetSniffPhrases.length)]); + } + if (Math.random() < 0.05 * (1 / (server.difficulty + 1))) { + const connectedServerName = server.serversOnNetwork[Math.floor(Math.random() * server.serversOnNetwork.length)]; + const connectedServer = getDarknetServer(connectedServerName); + if (connectedServer) { + return log(`Connecting to ${connectedServerName}:${connectedServer.password} ...`); + } + } + if (Math.random() < 0.05) { + const connectedServerName = server.serversOnNetwork[Math.floor(Math.random() * server.serversOnNetwork.length)]; + return log(`[sending transaction details to ${connectedServerName}.]`); + } + if (Math.random() < 0.1) { + const mostRecentAuthLog = getMostRecentAuthLog(server.hostname); + if (mostRecentAuthLog) { + return log(getExactCharactersHint(mostRecentAuthLog.passwordAttempted, server.password)); + } + } + + if (Math.random() < 0.1) { + return log(getRandomCharsInPassword(server.password)); + } + + if (Math.random() < 0.05) { + const servers = getAllMovableDarknetServers(); + const randomServer = servers[Math.floor(Math.random() * servers.length)]; + return log(`--${randomServer.password}--`); + } + + if (server.modelId === ModelIds.packetSniffer && Math.random() < 0.5 / (server.difficulty + 1)) { + return log("Authentication successful: " + server.password); + } + + return log(`${logDate.toLocaleTimeString()}: ${server.hostname} - heartbeat check (alive)`); +}; + +const log = (message: string, pid = -1) => ({ + message, + pid, +}); + +export const getMostRecentAuthLog = (hostname: string) => { + for (const log of getServerState(hostname).serverLogs) { + if (!isPasswordResponse(log.message)) { + continue; + } + return log.message; + } + return null; +}; + +export const getServerLogs = (server: DarknetServer, logLines: number, peek = false) => { + populateServerLogsWithNoise(server); + + const serverState = getServerState(server.hostname); + if (peek) { + return serverState.serverLogs.slice(0, logLines); + } + const logs = serverState.serverLogs.slice(0, logLines); + serverState.serverLogs = serverState.serverLogs.slice(logLines); + return logs; +}; diff --git a/src/DarkNet/ui/LabyrinthSummary.tsx b/src/DarkNet/ui/LabyrinthSummary.tsx new file mode 100644 index 000000000..2e6f41590 --- /dev/null +++ b/src/DarkNet/ui/LabyrinthSummary.tsx @@ -0,0 +1,195 @@ +import React, { useState } from "react"; +import { Typography, Select, MenuItem, Card } from "@mui/material"; +import { cleanUpLabyrinthLocations, DarknetState, getServerState, LogEntry } from "../models/DarknetState"; +import { + getLabMaze, + getLabyrinthDetails, + getLabyrinthLocationReport, + getSurroundingsVisualized, +} from "../effects/labyrinth"; +import { dnetStyles } from "./dnetStyles"; +import { Player } from "@player"; +import { useCycleRerender } from "../../ui/React/hooks"; +import { findRunningScriptByPid } from "../../Script/ScriptHelpers"; +import { GetServerOrThrow } from "../../Server/AllServers"; +import { assertPasswordResponse, isPasswordResponse } from "../models/DarknetServerOptions"; + +export type LabyrinthSummaryProps = { + isAuthenticating: boolean; +}; + +export const LabyrinthSummary = ({ isAuthenticating }: LabyrinthSummaryProps): React.ReactElement => { + const [currentPerspective, setCurrentPerspective] = useState(-1); + const { classes } = dnetStyles({}); + useCycleRerender(); + + const lab = getLabyrinthDetails(); + if (lab.cha > Player.skills.charisma) { + return You don't yet have the wits needed to attempt the labyrinth.; + } + + cleanUpLabyrinthLocations(); + const [x, y] = DarknetState.labLocations[currentPerspective] ? DarknetState.labLocations[currentPerspective] : [1, 1]; + const surroundings = getSurroundingsVisualized(getLabMaze(), x, y, 3, true, true) + .split("") + .map((c) => `${c}${c}${c}`) + .join("") + .replace("@@@", " @ ") + .replace("XXX", " X ") + .split("\n") + .map((line) => `${line}\n${line.replace("@", " ").replace("X", " ")}`) + .join(""); + + const getMenuItems = () => { + const darknetScripts = Object.keys(DarknetState.labLocations).map((pid) => findRunningScriptByPid(Number(pid))); + + const scriptOptions = []; + for (const script of darknetScripts) { + if (!script) { + continue; + } + const scriptServer = GetServerOrThrow(script.server); + const connectedToLab = scriptServer.serversOnNetwork.includes(lab.name); + scriptOptions.push( + + {`PID ${script.pid}: ${script.server} - ${!connectedToLab ? "(Not connected to lab)" : script.filename}`} + , + ); + } + + if (lab.manual) { + return [ + + Manual UI + , + ...scriptOptions, + ]; + } + return scriptOptions; + }; + + const getMenu = () => { + // With non-manual labyrinth, return immediately if there are no pids navigating the labyrinth. + if (Object.keys(DarknetState.labLocations).length === 1 && !lab.manual) { + return (No scripts found); + } + let perspective = currentPerspective; + // This happens when a script navigating the labyrinth dies. + if (perspective !== -1 && !DarknetState.labLocations[perspective]) { + perspective = -1; + } + // With non-manual labyrinth, if there are pids navigating the labyrinth and the perspective is not one of them, set + // the perspective to one of those pids. + if (perspective === -1 && !lab.manual) { + const ids = Object.keys(DarknetState.labLocations).filter((k) => Number(k) !== -1); + perspective = Number(ids[0]); + } + // Set the React state if necessary. + if (perspective !== currentPerspective) { + setCurrentPerspective(perspective); + } + + return ( + + ); + }; + + const getLogs = () => + getServerState(lab.name) + .serverLogs.filter((log) => log.pid === currentPerspective) + .slice(0, 2) + .map(stringifyLog) + .join("\n") || "(no response yet)"; + + const stringifyLog = (log: LogEntry) => { + if (typeof log.message === "string") return log.message; + const json = JSON.stringify(log.message, null, 2); + const surroundings = (log.message.data ?? "").replaceAll("\n", "\n "); + return json.replace(/("data": )("[^"]*")/g, `$1"${surroundings}"`); + }; + + const getManualFeedback = () => { + if (isAuthenticating) { + return "Travelling..."; + } + if (currentPerspective !== -1) { + return `You are following the progress of pid ${currentPerspective} instead of the manual mode.`; + } + const lastLog = getServerState(lab.name).serverLogs.find( + (log) => log.pid === -1 && isPasswordResponse(log.message), + ); + if (lastLog == null) { + return ""; + } + assertPasswordResponse(lastLog.message); + return lastLog.message.message; + }; + + const getLocationStatusString = () => { + const dataString = JSON.stringify(getLabyrinthLocationReport(currentPerspective)); + // Add a zero width space before the success flag so the text can wrap for better readability + return dataString.replace(`,"success"`, `,\u200B"success"`); + }; + + return ( + <> +
    +
    + {!lab.manual ? ( + + This lab cannot be completed manually. Select a script PID that is attempting the labyrinth from the + options below to view its progress. + + ) : ( + <> + + Manual mode feedback:
    + {getManualFeedback()} +
    + Current Surroundings: +
    {surroundings}
    + + Current Coordinates: {x},{y} + + + )} +
    +
    + + Logs scraped via
    heartbleed
    : +
    + +
    +
    {getLogs()}
    +
    +
    + + + ns.dnet.labreport: + + +
    +
    {getLocationStatusString()}
    +
    +
    +
    +
    + +
    +
    +
    + Script/perspective to follow: + {getMenu()} +
    + + ); +}; diff --git a/src/DarkNet/ui/NetworkDisplayWrapper.tsx b/src/DarkNet/ui/NetworkDisplayWrapper.tsx new file mode 100644 index 000000000..31dd164e9 --- /dev/null +++ b/src/DarkNet/ui/NetworkDisplayWrapper.tsx @@ -0,0 +1,368 @@ +import React, { + useEffect, + useRef, + useState, + useMemo, + useCallback, + type PointerEventHandler, + type WheelEventHandler, +} from "react"; +import { Container, Typography, Button, Box, Tooltip } from "@mui/material"; +import { ZoomIn, ZoomOut } from "@mui/icons-material"; +import { throttle } from "lodash"; +import { ServerStatusBox } from "./ServerStatusBox"; +import { useRerender } from "../../ui/React/hooks"; +import { DarknetEvents, DarknetState } from "../models/DarknetState"; +import { SpecialServers } from "../../Server/data/SpecialServers"; +import { drawOnCanvas, getPixelPosition } from "./networkCanvas"; +import { dnetStyles } from "./dnetStyles"; +import { getLabyrinthDetails, isLabyrinthServer } from "../effects/labyrinth"; +import { DarknetServer } from "../../Server/DarknetServer"; +import { getAllDarknetServers } from "../utils/darknetNetworkUtils"; +import { ServerDetailsModal } from "./ServerDetailsModal"; +import { AutoCompleteSearchBox } from "../../ui/AutoCompleteSearchBox"; +import { getDarknetServerOrThrow } from "../utils/darknetServerUtils"; +import { getServerLogs } from "../models/packetSniffing"; +import { getTimeoutChance } from "../effects/offlineServerHandling"; +import { DocumentationLink } from "../../ui/React/DocumentationLink"; +import { Settings } from "../../Settings/Settings"; + +const DW_NET_WIDTH = 6000; +const DW_NET_HEIGHT = 12000; +const initialSearchLabel = `Search:`; + +export function NetworkDisplayWrapper(): React.ReactElement { + const rerender = useRerender(); + const draggableBackground = useRef(null); + const canvas = useRef(null); + const [zoomIndex, setZoomIndex] = useState(DarknetState.zoomIndex); + const [netDisplayDepth, setNetDisplayDepth] = useState(1); + const [searchLabel, setSearchLabel] = useState(initialSearchLabel); + const [serverOpened, setServerOpened] = useState(null); + const zoomOptions = useMemo(() => [0.12, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.75, 1, 1.3], []); + const { classes } = dnetStyles({}); + const instability = getTimeoutChance(); + const instabilityText = instability > 0.01 ? `${(instability * 100).toFixed(1)}%` : "< 1%"; + + const scrollTo = useCallback( + (top: number, left: number) => { + DarknetState.netViewTopScroll = top; + DarknetState.netViewLeftScroll = left; + + draggableBackground?.current?.scrollTo({ + top: top, + left: left, + behavior: "instant", + }); + }, + [draggableBackground], + ); + + useEffect(() => { + const clearSubscription = DarknetEvents.subscribe(() => { + if (canvas.current) { + const lab = getLabyrinthDetails().lab; + const startingDepth = lab && getServerLogs(lab, 1, true).length ? lab.depth : 0; + const deepestServer = DarknetState.Network.flat().reduce((deepest, server) => { + if (server?.hasAdminRights && server.depth > deepest) { + return server.depth; + } + return deepest; + }, startingDepth); + const visibilityMargin = DarknetState.showFullNetwork ? 99 : 3; + setNetDisplayDepth(deepestServer + visibilityMargin); + + rerender(); + drawOnCanvas(canvas.current); + } + }); + canvas.current && drawOnCanvas(canvas.current); + draggableBackground.current?.addEventListener("wheel", (e) => e.preventDefault()); + scrollTo(DarknetState.netViewTopScroll, DarknetState.netViewLeftScroll); + + return () => { + clearSubscription(); + }; + }, [rerender, scrollTo]); + + useEffect(() => { + DarknetEvents.emit(); + }, []); + + const allowAuth = (server: DarknetServer | null) => + !!server && + (server.hasAdminRights || + server.serversOnNetwork.some((neighbor) => getDarknetServerOrThrow(neighbor).hasAdminRights)); + + const darkWebRoot = getDarknetServerOrThrow(SpecialServers.DarkWeb); + const labDetails = getLabyrinthDetails(); + const labyrinth = labDetails.lab; + const depth = labDetails.depth; + + const handleDragStart: PointerEventHandler = (pointerEvent) => { + const target = pointerEvent.target as HTMLDivElement; + const background = draggableBackground.current; + if (target.id === "draggableBackgroundTarget") { + background?.setPointerCapture(pointerEvent.pointerId); + } + }; + + const handleDragEnd: PointerEventHandler = (pointerEvent) => { + const target = pointerEvent.target as HTMLDivElement; + const background = draggableBackground.current; + if (target.id === "draggableBackgroundTarget") { + background?.releasePointerCapture(pointerEvent.pointerId); + } + DarknetEvents.emit(); + }; + + const handleDrag: PointerEventHandler = (pointerEvent) => { + const background = draggableBackground.current; + if (background?.hasPointerCapture(pointerEvent.pointerId)) { + scrollTo(background?.scrollTop - pointerEvent.movementY, (background?.scrollLeft ?? 0) - pointerEvent.movementX); + } + }; + + const zoomIn = useCallback(() => { + if (zoomIndex >= zoomOptions.length - 1) { + return; + } + DarknetState.zoomIndex = Math.max(zoomIndex + 1, 0); + setZoomIndex(DarknetState.zoomIndex); + const zoom = zoomOptions[zoomIndex]; + const background = draggableBackground.current; + scrollTo( + (background?.scrollTop ?? 0) + ((background?.clientHeight ?? 0) / 4) * zoom, + (background?.scrollLeft ?? 0) + ((background?.clientWidth ?? 0) / 4) * zoom, + ); + }, [zoomIndex, setZoomIndex, zoomOptions, scrollTo]); + + const zoomOut = useCallback(() => { + if (zoomIndex <= 0) { + return; + } + DarknetState.zoomIndex = Math.min(zoomIndex - 1, zoomOptions.length - 1); + setZoomIndex(DarknetState.zoomIndex); + const zoom = zoomOptions[zoomIndex]; + const background = draggableBackground.current; + scrollTo( + (background?.scrollTop ?? 0) - ((background?.clientHeight ?? 0) / 4) * zoom, + (background?.scrollLeft ?? 0) - ((background?.clientWidth ?? 0) / 4) * zoom, + ); + }, [zoomIndex, setZoomIndex, zoomOptions, scrollTo]); + + const zoom = useCallback( + (wheelEvent: WheelEvent) => { + const target = wheelEvent.target as HTMLDivElement; + if (!draggableBackground.current || DarknetState.openServer) { + return; + } + if (wheelEvent.deltaY < 0) { + zoomIn(); + } else { + zoomOut(); + } + + if (!target?.parentElement?.getBoundingClientRect()) { + return; + } + }, + [draggableBackground, zoomOut, zoomIn], + ); + + const zoomRef = useRef(zoom); + + useEffect(() => { + zoomRef.current = zoom; + }, [zoom]); + + // creating throttled callback only once - on mount + const throttledZoom = useMemo(() => { + const func = (wheelEvent: WheelEvent) => { + zoomRef.current?.(wheelEvent); + }; + return throttle(func, 200); + }, []); + + const handleZoom: WheelEventHandler = (wheelEvent) => { + wheelEvent.stopPropagation(); + throttledZoom(wheelEvent as unknown as WheelEvent); + }; + + const isWithinScreen = (server: DarknetServer) => { + const { left, top } = getPixelPosition(server, true); + const background = draggableBackground.current; + const buffer = 600; + const visibleAreaLeftEdge = (background?.scrollLeft ?? 0) / zoomOptions[zoomIndex]; + const visibleAreaTopEdge = (background?.scrollTop ?? 0) / zoomOptions[zoomIndex]; + const visibleAreaRightEdge = + visibleAreaLeftEdge + ((background?.clientWidth ?? 0) / zoomOptions[zoomIndex] ** 2 || window.innerWidth); + const visibleAreaBottomEdge = + visibleAreaTopEdge + ((background?.clientHeight ?? 0) / zoomOptions[zoomIndex] ** 2 || window.innerHeight); + return ( + left >= visibleAreaLeftEdge - buffer && + left <= visibleAreaRightEdge + buffer && + top >= visibleAreaTopEdge - buffer && + top <= visibleAreaBottomEdge + buffer + ); + }; + + const search = (selection: string, options: string[], searchTerm: string) => { + if (searchTerm.length === 1) { + return; + } // Ignore single character searches + if (!searchTerm) { + setSearchLabel(initialSearchLabel); + return; + } + const servers = getAllDarknetServers(); + const foundServer = + servers.find((s) => s.hostname.toLowerCase() === selection.toLowerCase()) || + servers.find((s) => s.hostname.toLowerCase() === options[0]?.toLowerCase()); + + if (!foundServer) { + setSearchLabel(`(No results)`); + return; + } else { + setSearchLabel(initialSearchLabel); + } + + const position = getPixelPosition(foundServer, true); + + const background = draggableBackground.current; + scrollTo( + position.top * zoomOptions[zoomIndex] - ((background?.clientHeight ?? 0) / 2 - 100), + position.left * zoomOptions[zoomIndex] - (background?.clientWidth ?? 0) / 2, + ); + + if (allowAuth(foundServer)) { + setServerOpened(foundServer); + } + }; + + const getAutocompleteSuggestionList = (): string[] => { + const servers = getAllDarknetServers() + .filter((s) => s.depth < netDisplayDepth && !isLabyrinthServer(s.hostname)) + .map((s) => s.hostname); + + if (labyrinth && netDisplayDepth > depth) { + return [...servers, labyrinth.hostname]; + } + + return servers; + }; + + return ( + + {serverOpened ? ( + setServerOpened(null)} + server={serverOpened} + classes={classes} + /> + ) : ( + "" + )} + {DarknetState.allowMutating ? ( + + + Dark Net + + {instability && ( + + If too many darknet servers are backdoored, it will increase the chance that authentication
    + attempts will return a 408 Request Timeout error (even if the password is correct).
    + Most servers will eventually restart or go offline, which removes backdoors over time. + + } + > + + {" "} + Instability: {instabilityText} + +
    + )} +
    + ) : ( + + [WEBSTORM WARNING] + + )} + +
    +
    + + {darkWebRoot && } + {DarknetState.Network.slice(0, netDisplayDepth).map((row, i) => + row.map( + (server, j) => + server && + isWithinScreen(server) && ( + + ), + ), + )} + + {labyrinth && netDisplayDepth > depth && ( + + )} +
    +
    +
    + + +
    + + + + {searchLabel} + + { + search(selection, options, searchValue); + }} + width={300} + /> + + + Darknet Docs + + +
    + ); +} diff --git a/src/DarkNet/ui/PasswordPrompt.tsx b/src/DarkNet/ui/PasswordPrompt.tsx new file mode 100644 index 000000000..c1a83ba72 --- /dev/null +++ b/src/DarkNet/ui/PasswordPrompt.tsx @@ -0,0 +1,186 @@ +import React, { useState, useRef } from "react"; +import { Button, Container, Card, TextField, Typography } from "@mui/material"; +import { getPasswordType } from "../controllers/ServerGenerator"; +import { dnetStyles } from "./dnetStyles"; +import type { DarknetResult } from "@nsdefs"; +import { getAuthResult } from "../effects/authentication"; +import { DarknetEvents } from "../models/DarknetState"; +import { LabyrinthSummary } from "./LabyrinthSummary"; +import { getLabyrinthDetails, isLabyrinthServer } from "../effects/labyrinth"; +import { ModelIds } from "../Enums"; +import { sleep } from "../../utils/Utility"; +import { getSharedChars } from "../utils/darknetAuthUtils"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { formatObjectWithColoredKeys } from "./uiUtilities"; + +export type PasswordPromptProps = { + server: DarknetServer; + onClose: () => void; +}; + +export const PasswordPrompt = ({ server, onClose }: PasswordPromptProps): React.ReactElement => { + const [inputPassword, setInputPassword] = useState(server.hasAdminRights ? server.password : ""); + const [isAuthenticating, setIsAuthenticating] = useState(false); + const [lastDarknetResultFromAuth, setLastDarknetResultFromAuth] = useState(null); + const { classes } = dnetStyles({}); + + const passwordInput = useRef(null); + const isLabServer = isLabyrinthServer(server.hostname); + const canEnterLabManually = getLabyrinthDetails().manual; + const disablePasswordInput = (!canEnterLabManually && isLabServer) || server.hasAdminRights; + + if (isLabServer && !canEnterLabManually) { + return ( + <> +
    +
    + + The weight of the deep net presses down on you. It seems this place will challenge you to make your own + tools... + +
    +
    + + + ); + } + + async function attemptPassword(passwordAttempted: string): Promise { + setIsAuthenticating(true); + + const sharedChars = + server.modelId === ModelIds.TimingAttack ? getSharedChars(server.password, passwordAttempted) : 0; + const responseTime = 500 + sharedChars * 150; + await sleep(responseTime); + // Cancel if the component unmounted while waiting + if (passwordInput.current === null) { + return; + } + + // Manual password entry counts as having two threads, to increase the cha xp slightly during early exploration + const authResult = getAuthResult(server, passwordAttempted, 2, responseTime); + + // Do NOT carelessly move these setters, especially when moving them to after DarknetEvents.emit(). + // DarknetEvents.emit() makes the parent component rerender, so this component may be unmounted. In that case, + // these calls will set the states of an unmounted component. + setIsAuthenticating(false); + setLastDarknetResultFromAuth(authResult.result); + + if (authResult.result.success) { + DarknetEvents.emit("server-unlocked", server); + } else { + // This selects the text inside the password input field so that the player can immediately start typing a new + // guess without needing to clear out the old one. + // Do NOT move this line below DarknetEvents.emit(). DarknetEvents.emit() may make this component unmounted, so + // passwordInput.current may become null unexpectedly. Using the optional chaining operator for accessing + // passwordInput.current is specifically for the case in which somebody mistakenly moves this line. + passwordInput.current?.querySelector("input")?.select(); + DarknetEvents.emit(); + } + } + + const handleSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + if (server.hasAdminRights) { + onClose(); + return; + } + if (!isAuthenticating) { + attemptPassword(inputPassword).catch((error) => console.error(error)); + } + }; + + let authFeedback; + if (isAuthenticating) { + authFeedback = "Checking password..."; + } else { + if (lastDarknetResultFromAuth === null) { + authFeedback = "(no response yet)"; + } else { + authFeedback = formatObjectWithColoredKeys(lastDarknetResultFromAuth, ["success", "message", "code"]); + } + } + + return ( + <> +
    +
    +
    handleSubmit(e)}> + setInputPassword(e.target.value)} + variant="outlined" + autoComplete="off" + autoFocus={!server.hasAdminRights} + disabled={disablePasswordInput} + /> + +
    + +
    +
    +
    + {!isLabServer && ( + + Logs scraped via
    heartbleed
    : +
    + )} +
    +
    + + {isLabServer ? ( +
    +
    +                  Hint: {server.staticPasswordHint}
    +                  
    + Model: {server.modelId} +
    + Required charisma: {server.requiredCharismaSkill} +
    +
    +
    + ) : ( +
    +
    +                  Hint: {server.staticPasswordHint}
    +                  
    + {server.passwordHintData && ( + <> + Data: {server.passwordHintData} +
    + + )} + Length: {server.password.length} +
    + Format: {getPasswordType(server.password)} +
    + Model: {server.modelId} +
    +
    +
    + )} +
    +
    + {!isLabServer && ( + +
    + {typeof authFeedback !== "string" ? ( + authFeedback + ) : ( +
    {authFeedback}
    + )} +
    +
    + )} +
    +
    +
    + {isLabServer && } + + ); +}; diff --git a/src/DarkNet/ui/ServerDetailsModal.tsx b/src/DarkNet/ui/ServerDetailsModal.tsx new file mode 100644 index 000000000..2906067ad --- /dev/null +++ b/src/DarkNet/ui/ServerDetailsModal.tsx @@ -0,0 +1,115 @@ +import React from "react"; +import { Modal } from "../../ui/React/Modal"; +import { Container, Card, SvgIcon, Typography, Tooltip } from "@mui/material"; +import { getIcon } from "./ServerIcon"; +import { getServerState } from "../models/DarknetState"; +import { ServerSummary } from "./ServerSummary"; +import { populateServerLogsWithNoise } from "../models/packetSniffing"; +import { isLabyrinthServer } from "../effects/labyrinth"; +import { PasswordPrompt } from "./PasswordPrompt"; +import { copyToClipboard, formatObjectWithColoredKeys, formatToMaxDigits } from "./uiUtilities"; +import { useCycleRerender } from "../../ui/React/hooks"; +import type { DarknetServer } from "../../Server/DarknetServer"; +import { logBoxBaseZIndex } from "../../ui/React/Constants"; + +export type DWPasswordPromptModalProps = { + open: boolean; + onClose: () => void; + server: DarknetServer; + classes: { + [key: string]: string; + }; +}; + +export const ServerDetailsModal = ({ + open, + onClose, + server, + classes, +}: DWPasswordPromptModalProps): React.ReactElement => { + useCycleRerender(); + + const icon = getIcon(server.modelId); + populateServerLogsWithNoise(server); + const serverState = getServerState(server.hostname); + const isLabServer = isLabyrinthServer(server.hostname); + const recentLogs = serverState.serverLogs.slice(0, 5); + const ramBlock = server.blockedRam; + const blockedRamString = ramBlock ? formatToMaxDigits(ramBlock, 1) + "+" : ""; + const usedRamString = formatToMaxDigits(server.ramUsed - ramBlock, 1); + const serverRamString = `RAM in use: ${blockedRamString}${usedRamString}/${server.maxRam} GB`; + + const logContent = recentLogs.map((log, index) => ( +
    +      {typeof log.message === "string"
    +        ? log.message
    +        : formatObjectWithColoredKeys(log.message, [
    +            "message",
    +            "data",
    +            "passwordAttempted",
    +            "passwordExpected",
    +            "code",
    +          ])}
    +    
    + )); + + const copyHostname = () => copyToClipboard(server.hostname); + + return ( + + <> + +
    + + {server.hostname} + + + + +
    +
    + {server.hasAdminRights ? ( + <> + Password: "{server.password}" +
    + IP: {server.ip} + Required charisma: {server.requiredCharismaSkill} + + {serverRamString} + + Model: {server.modelId} +
    +
    + +
    +
    + {isLabServer && ( + <> +
    + You have successfully navigated the labyrinth! Congratulations! + + )} + + ) : ( + + )} + {!isLabServer && ( + <> + +
    {logContent}
    +
    + + )} +
    + +
    + ); +}; diff --git a/src/DarkNet/ui/ServerIcon.ts b/src/DarkNet/ui/ServerIcon.ts new file mode 100644 index 000000000..00ccd6ddc --- /dev/null +++ b/src/DarkNet/ui/ServerIcon.ts @@ -0,0 +1,86 @@ +import { + ConnectedTv, + LaptopMac, + DesktopMac, + Dns, + PhoneIphone, + Terminal, + SatelliteAlt, + Dvr, + Microwave, + ElectricCar, + Blender, + LiveTv, + Subtitles, + Web, + ExitToApp, + SignalWifiStatusbarConnectedNoInternet4, + Calculate, + Watch, + NoCell, + SettingsPower, + VideogameAsset, + AccountBalance, + Elevator, + Fax, + AssuredWorkload, + SvgIconComponent, +} from "@mui/icons-material"; +import { ModelIds } from "../Enums"; + +export const getIcon = (model: string): SvgIconComponent => { + switch (model) { + case ModelIds.EchoVuln: + return ConnectedTv; + case ModelIds.SortedEchoVuln: + return LaptopMac; + case ModelIds.NoPassword: + return PhoneIphone; + case ModelIds.Captcha: + return Dns; + case ModelIds.DefaultPassword: + return LiveTv; + case ModelIds.BufferOverflow: + return Terminal; + case ModelIds.MastermindHint: + return SatelliteAlt; + case ModelIds.TimingAttack: + return Fax; + case ModelIds.LargestPrimeFactor: + return Calculate; + case ModelIds.RomanNumeral: + return Watch; + case ModelIds.DogNames: + return DesktopMac; + case ModelIds.GuessNumber: + return Dvr; + case ModelIds.CommonPasswordDictionary: + return Subtitles; + case ModelIds.EUCountryDictionary: + return Web; + case ModelIds.Yesn_t: + return NoCell; + case ModelIds.BinaryEncodedFeedback: + return SettingsPower; + case ModelIds.SpiceLevel: + return Microwave; + case ModelIds.ConvertToBase10: + return VideogameAsset; + case ModelIds.parsedExpression: + return AccountBalance; + case ModelIds.divisibilityTest: + return ElectricCar; + case ModelIds.tripleModulo: + return Blender; + case ModelIds.globalMaxima: + return Elevator; + case ModelIds.packetSniffer: + return SignalWifiStatusbarConnectedNoInternet4; + case ModelIds.encryptedPassword: + return AssuredWorkload; + case ModelIds.labyrinth: + return ExitToApp; + default: + return ConnectedTv; + } +}; diff --git a/src/DarkNet/ui/ServerStatusBox.tsx b/src/DarkNet/ui/ServerStatusBox.tsx new file mode 100644 index 000000000..9d1cd802d --- /dev/null +++ b/src/DarkNet/ui/ServerStatusBox.tsx @@ -0,0 +1,71 @@ +import React, { useState } from "react"; +import { Typography, SvgIcon, Tooltip } from "@mui/material"; +import { ServerDetailsModal } from "./ServerDetailsModal"; +import { getIcon } from "./ServerIcon"; +import { DarknetState } from "../models/DarknetState"; +import { getPixelPosition } from "./networkCanvas"; +import { ServerSummary } from "./ServerSummary"; + +import type { DarknetServer } from "../../Server/DarknetServer"; +import { DWServerStyles, ServerName } from "./dnetStyles"; + +export type DWServerProps = { + server: DarknetServer; + enableAuth: boolean; + classes: { + [key: string]: string; + }; +}; + +export function ServerStatusBox({ server, enableAuth, classes }: DWServerProps): React.ReactElement { + const [open, setOpen] = useState(false); + const icon = getIcon(server.modelId); + + const authButtonHandler = () => { + DarknetState.openServer = server; + setOpen(true); + }; + + const handleClose = () => { + DarknetState.openServer = null; + setOpen(false); + }; + + const getServerStyles = (server: DarknetServer) => { + const position = getPixelPosition(server); + return { + ...DWServerStyles, + top: `${position.top}px`, + left: `${position.left}px`, + borderColor: server.hasStasisLink ? "gold" : server.hasAdminRights ? "green" : "grey", + }; + }; + + return ( + <> + {open ? : ""} + + + ); +} diff --git a/src/DarkNet/ui/ServerSummary.tsx b/src/DarkNet/ui/ServerSummary.tsx new file mode 100644 index 000000000..44c2e3c18 --- /dev/null +++ b/src/DarkNet/ui/ServerSummary.tsx @@ -0,0 +1,151 @@ +import React from "react"; +import { SvgIcon, Tooltip, Typography } from "@mui/material"; +import { Code, Description, Inventory2, LockPerson, Terminal, Bolt, DoorBackSharp } from "@mui/icons-material"; +import { formatNumber } from "../../ui/formatNumber"; +import { CompletedProgramName } from "@enums"; +import { formatToMaxDigits } from "./uiUtilities"; + +import type { DarknetServer } from "../../Server/DarknetServer"; +import { DarknetConstants } from "../Constants"; + +export type ServerSummaryProps = { + server: DarknetServer; + enableAuth: boolean; + showDetails?: boolean; + classes: { + [key: string]: string; + }; +}; + +export function ServerSummary({ + server, + enableAuth, + classes, + showDetails = false, +}: ServerSummaryProps): React.ReactElement { + if (!server.hasAdminRights && enableAuth) { + return [ auth required ]; + } + if (!server.hasAdminRights && !enableAuth) { + return (no connection); + } + + const cacheCount = server.caches.length; + const dataFiles = Array.from(server.textFiles.keys()).filter((f) => f.endsWith(DarknetConstants.DataFileSuffix)); + const textFiles = [...dataFiles, ...server.messages]; + const fileCount = textFiles.length; + const textFilesTooltip = + textFiles.length > 0 + ? `Data files on server: ${textFiles.slice(0, 3).join(", ")}${ + textFiles.length > 3 ? ` +${textFiles.length - 3}` : "" + }` + : "No data files on server"; + const contractCount = server.contracts.length; + const runningScriptNames = Array.from(server.runningScriptMap.keys()).map((script) => script.replace("*[]", "")); + const runningScriptsTooltip = + runningScriptNames.length > 0 + ? `Running scripts on server: ${runningScriptNames.slice(0, 3).join(", ")}${ + runningScriptNames.length > 3 ? ` +${runningScriptNames.length - 3}` : "" + }` + : "No running scripts on server"; + const hasStormSeed = server.programs.includes(CompletedProgramName.stormSeed); + const hasBackdoor = server.backdoorInstalled && !server.hasStasisLink; + const ramBlockedDetails = formatToMaxDigits(server.blockedRam, 2) + "GB"; + const ramBlocked = showDetails ? ramBlockedDetails : formatNumber(server.blockedRam, 0); + + const runningScriptsComponent = ( + {runningScriptsTooltip}}> + 0 ? "primary" : "secondary"}> + + {runningScriptNames.length} + + + ); + + const components = []; + if (cacheCount) { + components.push( + Reward cache count: {cacheCount}}> + + + {cacheCount} + + , + ); + } + if (hasStormSeed) { + components.push( + A mysterious executable has been found here...}> + + ? + + , + ); + } + if (hasBackdoor) { + components.push( + Backdoor installed. Warning: this increases darknet instability.}> + + + + , + ); + } + if (server.hasStasisLink) { + components.push( + + Stasis link installed. This allows connecting to the server remotely, as well as ns.exec from any distance. + + } + > + + + + , + ); + } + if (contractCount) { + components.push( + Coding contract count: {contractCount}}> + + + {contractCount} + + , + ); + } + if (fileCount) { + components.push( + {textFilesTooltip}}> + + + {fileCount} + + , + ); + } + if (server.blockedRam) { + components.push( + Ram blocked by owner: {ramBlockedDetails}. This can be freed up using ns.dnet.memoryReallocation()} + > + + + {ramBlocked} + + , + ); + } + const maxIcons = showDetails ? components.length : 2; + const componentsToShow = [...components.slice(0, maxIcons), runningScriptsComponent]; + + return ( +
    + {componentsToShow} +
    + ); +} diff --git a/src/DarkNet/ui/dnetStyles.ts b/src/DarkNet/ui/dnetStyles.ts new file mode 100644 index 000000000..0d22a1c27 --- /dev/null +++ b/src/DarkNet/ui/dnetStyles.ts @@ -0,0 +1,161 @@ +import { Theme } from "@mui/material/styles"; +import { makeStyles } from "tss-react/mui"; + +export const dwColors = ["hack", "hp", "money", "int", "cha", "rep", "success"] as const; +export type dwColors = (typeof dwColors)[number]; + +export const DW_SERVER_WIDTH = 240; +export const DW_SERVER_HEIGHT = 130; +export const DW_SERVER_GAP_TOP = 120; +export const DW_SERVER_GAP_LEFT = 60; +export const MAP_BORDER_WIDTH = 300; + +export const dnetStyles = makeStyles({ uniqId: "dnetStyles" })((theme: Theme, __, __classes) => ({ + DWServer: { + "&:hover": { + backgroundColor: "#333 !important", + }, + }, + NetWrapper: { + width: "100%", + height: "calc(100vh - 80px)", + overflow: "scroll", + position: "relative", + border: "solid 1px slategray", + }, + button: { + color: theme.colors.white, + }, + maze: { + color: theme.colors.white, + lineHeight: 0.55, + }, + hiddenInput: { + width: 0, + height: 0, + padding: 0, + margin: 0, + opacity: 0, + }, + zoomContainer: { + position: "absolute", + top: "calc(90vh - 38px)", + marginLeft: "1px", + display: "grid", + zIndex: 20, + ["& > button"]: { + width: "40px", + minWidth: "40px !important", + }, + }, + inlineFlexBox: { + display: "inline-flex", + flexDirection: "row", + width: "100%", + justifyContent: "space-between", + }, + noPadding: { + padding: 0, + }, + paddingRight: { + paddingRight: "3px", + }, + serverStatusIcon: { + paddingRight: "3px", + position: "relative", + bottom: "-4px", + }, + gold: { + color: theme.colors.money, + }, + red: { + color: theme.colors.hp, + }, + white: { + color: theme.colors.white, + }, + authButton: { + ["&:disabled"]: { + opacity: 0.5, + }, + }, + hack: { + borderColor: theme.colors.hack, + }, + hp: { + borderColor: theme.colors.hp, + }, + money: { + borderColor: theme.colors.money, + }, + int: { + borderColor: theme.colors.int, + }, + cha: { + borderColor: theme.colors.cha, + }, + rep: { + borderColor: theme.colors.rep, + }, + success: { + borderColor: theme.colors.success, + }, + green: { + borderColor: "green", + }, + grey: { + borderColor: "grey", + }, + goldBorder: { + borderColor: "gold", + }, + serverDetailsText: { + marginLeft: "-2em", + textIndent: "2em", + color: "grey", + }, +})); + +/* + React by default creates a new