mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
707 lines
24 KiB
TypeScript
707 lines
24 KiB
TypeScript
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 = Math.min((alphanumeric ? 0 : 3) + difficulty / 4, 8);
|
||
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 + Math.random() * 6, 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();
|
||
};
|