├── .gitignore ├── .prettierignore ├── src ├── declarations │ └── escodegen.d.ts ├── consts.ts ├── deobfuscate.ts ├── fetchSocial.ts ├── processCode.ts ├── processWorker.ts ├── process.ts ├── libRenameVars.ts ├── libDecompile.ts └── generateRandomWords.ts ├── tsconfig.json ├── LICENSE ├── .eslintrc.json ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | processed 2 | unpacked 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | processed 2 | renamed 3 | unpacked 4 | node_modules 5 | dist 6 | debug 7 | deobfuscated.js -------------------------------------------------------------------------------- /src/declarations/escodegen.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@javascript-obfuscator/escodegen" { 2 | export * from "escodegen"; 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "sourceMap": true, 9 | "esModuleInterop": true, 10 | "outDir": "dist" 11 | }, 12 | "include": ["./src/**/*.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from "node:url"; 2 | 3 | export const deobfuscated = fileURLToPath( 4 | new URL("../deobfuscated.js", import.meta.url) 5 | ); 6 | export const unpackedDir = fileURLToPath( 7 | new URL("../unpacked/", import.meta.url) 8 | ); 9 | export const processedDir = fileURLToPath( 10 | new URL("../processed/", import.meta.url) 11 | ); 12 | export const renamedDir = fileURLToPath( 13 | new URL("../renamed/", import.meta.url) 14 | ); 15 | -------------------------------------------------------------------------------- /src/deobfuscate.ts: -------------------------------------------------------------------------------- 1 | import "source-map-support/register.js"; 2 | import { unpackedDir } from "./consts.js"; 3 | import { readFile } from "node:fs/promises"; 4 | import { argv } from "node:process"; 5 | import { rimraf } from "rimraf"; 6 | import { webcrack } from "webcrack"; 7 | 8 | const [, , script] = argv; 9 | let code = await readFile(script, "utf-8"); 10 | 11 | const res = await webcrack(code, { unpack: true }); 12 | console.log("Deobfuscated. Saving unpacked modules."); 13 | await rimraf(unpackedDir); 14 | await res.save(unpackedDir); 15 | // await writeFile(deobfuscated, code); 16 | -------------------------------------------------------------------------------- /src/fetchSocial.ts: -------------------------------------------------------------------------------- 1 | import { writeFile } from "node:fs/promises"; 2 | import { argv } from "node:process"; 3 | 4 | // you need im_ after the numbers so they don't mess with the JS or anything 5 | // example custom URL: https://web.archive.org/web/20210322135140im_/https://krunker.io/social.html 6 | const [, , destination, url = "https://krunker.io/social.html"] = argv; 7 | 8 | if (!destination) throw new TypeError("Need output destination"); 9 | 10 | const res = await fetch(url); 11 | 12 | const [, script] = 13 | (await res.text()).match( 14 | /(\/\*!\n \*[\s\S]*?)<\/script>/ 15 | ) || []; 16 | 17 | if (!script) throw new TypeError("No script"); 18 | 19 | await writeFile(destination, script); 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2023 David Reed 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2022, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "node": true 9 | }, 10 | "plugins": ["@typescript-eslint", "prettier", "import"], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "plugin:prettier/recommended" 16 | ], 17 | "rules": { 18 | "prefer-const": "error", 19 | "@typescript-eslint/no-unused-expressions": "error", 20 | "no-use-before-define": "off", 21 | "no-useless-constructor": "error", 22 | "no-constant-condition": "off", 23 | "eqeqeq": "error", 24 | "@typescript-eslint/explicit-module-boundary-types": "off", 25 | "@typescript-eslint/no-unused-vars": "error", 26 | "@typescript-eslint/consistent-type-imports": "error", 27 | "import/no-anonymous-default-export": "error" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/processCode.ts: -------------------------------------------------------------------------------- 1 | import "source-map-support/register.js"; 2 | import { processCode } from "./processWorker.js"; 3 | import { writeFile, readFile } from "node:fs/promises"; 4 | import { webcrack } from "webcrack"; 5 | 6 | const programArgv = [...process.argv.slice(2)]; // clone the array to modify it 7 | 8 | const [file, output] = programArgv; 9 | 10 | if (!file) { 11 | console.log(`${process.argv.slice(0, 2).join(" ")} [|] [] 12 | 13 | Arguments: 14 | Path to a file containing the code 15 | The actual code (if not using a file) 16 | Location to output the code (default: stdout)`); 17 | process.exit(0); 18 | } 19 | 20 | let code: string; 21 | 22 | try { 23 | // try reading it as a file 24 | code = await readFile(file, "utf-8"); 25 | } catch { 26 | // use it as code 27 | code = file; 28 | } 29 | 30 | const { log } = console; 31 | console.log = () => { 32 | // noop 33 | }; 34 | ({ code } = await webcrack(code)); 35 | console.log = log; 36 | 37 | const parsed = processCode(code); 38 | 39 | if (output) { 40 | await writeFile(output, parsed); 41 | } else { 42 | console.log(parsed); 43 | } 44 | -------------------------------------------------------------------------------- /src/processWorker.ts: -------------------------------------------------------------------------------- 1 | import "source-map-support/register.js"; 2 | import { processedDir } from "./consts.js"; 3 | import decompile from "./libDecompile.js"; 4 | import renameVars from "./libRenameVars.js"; 5 | import escodegen from "@javascript-obfuscator/escodegen"; 6 | import { parse as parseScript } from "acorn"; 7 | import type { namedTypes as n } from "ast-types"; 8 | import crc32 from "crc-32"; 9 | import { readFile, writeFile } from "node:fs/promises"; 10 | import { join, parse } from "node:path"; 11 | 12 | export function processCode(code: string) { 13 | let program = parseScript(code, { 14 | ecmaVersion: "latest", 15 | allowReturnOutsideFunction: true, 16 | allowImportExportEverywhere: true, 17 | }) as n.Node as n.Program; 18 | 19 | decompile(program); 20 | 21 | code = escodegen.generate(program); 22 | 23 | program = parseScript(code, { 24 | ecmaVersion: "latest", 25 | ranges: true, 26 | allowReturnOutsideFunction: true, 27 | allowImportExportEverywhere: true, 28 | }) as n.Node as n.Program; 29 | 30 | const hash = crc32.str(code); 31 | 32 | renameVars(program, hash); 33 | 34 | return escodegen.generate(program); 35 | } 36 | 37 | export default async function processWorker(file: string) { 38 | const name = parse(file).name; 39 | const code = await readFile(file, "utf-8"); 40 | 41 | try { 42 | await writeFile(join(processedDir, `${name}.js`), processCode(code)); 43 | } catch (err) { 44 | console.error("Failure processing:", name); 45 | throw err; 46 | } 47 | 48 | console.log("Wrote", name); 49 | } 50 | -------------------------------------------------------------------------------- /src/process.ts: -------------------------------------------------------------------------------- 1 | import "source-map-support/register.js"; 2 | import { processedDir, unpackedDir } from "./consts.js"; 3 | import { mkdir, readFile } from "node:fs/promises"; 4 | import { join, parse } from "node:path"; 5 | import P from "piscina"; 6 | import prettyMilliseconds from "pretty-ms"; 7 | import { rimraf } from "rimraf"; 8 | 9 | const bundle = JSON.parse( 10 | await readFile(join(unpackedDir, "bundle.json"), "utf-8") 11 | ) as { 12 | type: "webpack" | "browserify"; 13 | entryId: string; 14 | modules: { 15 | id: string; 16 | path: string; 17 | }[]; 18 | }; 19 | 20 | const pool = new P.Piscina({ 21 | filename: new URL("./processWorker.js", import.meta.url).toString(), 22 | maxQueue: 100000, 23 | concurrentTasksPerWorker: 3, 24 | }); 25 | 26 | await rimraf(processedDir); 27 | await mkdir(processedDir); 28 | 29 | type Data = [path: string, promise: Promise, resolved: boolean]; 30 | 31 | const promises: Data[] = []; 32 | 33 | for await (const ent of bundle.modules) { 34 | const path = join(unpackedDir, ent.path); 35 | const data: Data = [path, pool.run(path), false]; 36 | promises.push(data); 37 | data[1].finally(() => (data[2] = true)); 38 | } 39 | 40 | const start = Date.now(); 41 | 42 | const interval = setInterval(() => { 43 | console.log( 44 | `[${prettyMilliseconds(Date.now() - start).padEnd( 45 | 7, 46 | " " 47 | )}] Waiting for: ${promises 48 | .filter((d) => !d[2]) 49 | .map((d) => parse(d[0]).name) 50 | .join(", ")}` 51 | ); 52 | }, 10e3); 53 | 54 | await Promise.all(promises.map(([, promise]) => promise)); 55 | 56 | clearInterval(interval); 57 | 58 | console.log("Completed"); 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "krunker-decompiler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "private": true, 7 | "type": "module", 8 | "scripts": { 9 | "fetchSocial": "node dist/fetchSocial.js", 10 | "deobfuscate": "node dist/deobfuscate.js", 11 | "unpack": "node dist/unpack.js", 12 | "process": "node dist/process.js", 13 | "processCode": "node dist/processCode.js", 14 | "build": "tsc", 15 | "watch": "tsc -w" 16 | }, 17 | "dependencies": { 18 | "@babel/helper-validator-identifier": "^7.19.1", 19 | "@javascript-obfuscator/escodegen": "^2.3.0", 20 | "@types/eslint": "^8.37.0", 21 | "acorn": "^8.8.2", 22 | "ast-types": "^0.14.2", 23 | "camel-case": "^4.1.2", 24 | "crc-32": "^1.2.2", 25 | "eslint-scope": "^7.1.1", 26 | "mersenne-twister": "^1.1.0", 27 | "pascal-case": "^3.1.2", 28 | "piscina": "^3.2.0", 29 | "pretty-ms": "^8.0.0", 30 | "rimraf": "^5.0.0", 31 | "source-map-support": "^0.5.21", 32 | "webcrack": "^2.11.1" 33 | }, 34 | "devDependencies": { 35 | "@ianvs/prettier-plugin-sort-imports": "^3.7.2", 36 | "@types/babel__core": "^7.20.0", 37 | "@types/babel__helper-validator-identifier": "^7.15.0", 38 | "@types/escodegen": "^0.0.7", 39 | "@types/eslint-scope": "^3.7.4", 40 | "@types/mersenne-twister": "^1.1.2", 41 | "@types/node": "^18.16.9", 42 | "@types/source-map-support": "^0.5.6", 43 | "@typescript-eslint/eslint-plugin": "^5.59.5", 44 | "@typescript-eslint/parser": "^5.59.5", 45 | "eslint": "^8.40.0", 46 | "eslint-config-prettier": "^8.8.0", 47 | "eslint-plugin-import": "^2.27.5", 48 | "eslint-plugin-prettier": "^4.2.1", 49 | "prettier": "^2.8.8", 50 | "typescript": "^5.0.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Krunker Decompiler 2 | 3 | [See an example](https://gist.github.com/e9x/eea9ecb2c0ce6fe0517ff522e78d282a) 4 | 5 | Powered by [webcrack](https://github.com/j4k0xb/webcrack) 6 | 7 | ## Quickstart 8 | 9 | 1. Get the repository & dependencies 10 | 11 | ``` 12 | git clone https://github.com/e9x/krunker-decompiler.git 13 | cd krunker-decompiler 14 | npm install 15 | ``` 16 | 17 | 2. Build the decompiler 18 | 19 | ``` 20 | npm run build 21 | ``` 22 | 23 | 2. Get the game source 24 | 25 | ``` 26 | [user@linux krunker-decompiler]$ cat ../krunker.05.12.23.js 27 | (function(iÌïiîií,iÏïïîiî){var iIiííìi=a3iíiíîiî,iÎìîìíì=iÌïiîií();while(!![]){try{var iIiiîîí=-parseInt(iIiííìi(0xc99))/0x1*(-parseInt(iIiííìi(0x172c))/0x2)+parseInt(iIiííìi(0xc3e))/0x3*(parseInt(iIiííìi(0x1623))/0x4)+-parseInt(iIiííìi(0x40a))/0x5+parseInt(iIiííìi(0x19b1))/0x6+-parseInt(iIiííìi(0x4888))/0x7+-parseInt(iIiííìi(0x45f8))/0x8*(-parseInt(iIiííìi(0xc7d))/0x9)+-parseInt(iIiííìi(0x2d1a))/0xa*(parseInt(iIiííìi(0x2699))/0xb);if(iIiiîîí===iÏïïîiî)break;else iÎìîìíì['push'](iÎìîìíì['shift']());}catch(iÏiîiií){iÎìîìíì['push'](iÎìîìíì['shift']());}}}(a3iìiìïìi,0x7bbf4),function(iïíîïii){var iiïîììí=(function(){var iiíiìií=!![];return function(iîìiíiì,iîiìiìî){var iiïîiìí=iiíiìií?function(){if(iîiìiìî){var iíïîïïí= 28 | ``` 29 | 30 | Make sure your source: 31 | 32 | - Is not wrapped in an anonymous function 33 | You can modify the source to remove the wrapper. 34 | 35 | ```js 36 | (function anonymous(b475796ed633d5fd0485 37 | ) { 38 | 39 | (/* source goes here */) 40 | }) 41 | ``` 42 | 43 | - Starts with a newline 44 | This is to match the real bundle. The bundle itself starts with a newline. However, this is optional. 45 | 46 | 3. Deobfuscate the source 47 | 48 | This will deobfuscate the soruce and split each module into an individual file. 49 | 50 | ```sh 51 | npm run deobfuscate ../krunker.js 52 | ``` 53 | 54 | 4. Process the modules 55 | 56 | This will undo all minifications to the source and rename the variables. 57 | 58 | ```sh 59 | npm run process 60 | ``` 61 | 62 | You can find the result in the `processed` directory. 63 | 64 | ## Get the game source (Chromium) 65 | 66 | 1. Go to Krunker 67 | 2. Open devtools 68 | 3. Refresh the tab 69 | 4. Click on "Network" 70 | 5. Find "seek-game?hostname=krunker.io..." 71 | 6. Click on "Initiator" 72 | 7. Click on the link to "VM XXX:X" 73 | A VM is a JavaScript virtual machine. It's assigned a number for debugging. 74 | 8. Wait for the VM's source to load. This may take up to 5 minutes depending on your machine. 75 | 9. Right click the tab in the source viewer. It should be named "VM XXX" 76 | 10. Select "Save as..." 77 | 11. Enter a name that ends with .js, like `krunker.js` 78 | 12. Close Devtools and Krunker 79 | 80 | ### Fixing the source for use in the decompiler 81 | 82 | Because the source is wrapped in a way that the decompiler doesn't understand, you'll have to modify it. 83 | 84 | 1. Open the source in an editor capable of handling an 8 MB file 85 | Like VS Code. 86 | 87 | 2. Remove the first 2 lines: 88 | 89 | ```js 90 | (function anonymous(b475796ed633d5fd0485 91 | ) { 92 | ``` 93 | 94 | 3. Remove the last line: 95 | 96 | ```js 97 | }) 98 | ``` 99 | 100 | 4. Save 101 | 102 | ## How to find the Webpack entry point 103 | 104 | Entry point is named `index.js` in the processed folder. 105 | 106 | ## How to decompile other files (Not Krunker/Webpack) 107 | 108 | This tool can be used against other scripts that aren't bundled with Webpack. For example, you might want to use this to rename variables or deobfuscate code in a script. 109 | 110 | 1. Build the program (if you haven't already) 111 | 112 | ```sh 113 | npm install 114 | npm run build 115 | ``` 116 | 117 | 2. Run the processCode script 118 | 119 | ```sh 120 | npm run processCode "function _0x1e3586(x,y){return x+y}exports.test = _0x1e3586;" 121 | ``` 122 | 123 | Ideally, the output should go to a file. 124 | 125 | ```sh 126 | npm run processCode "..." output.js 127 | ``` 128 | 129 | See output.js in the folder. 130 | 131 | For additional usage, run `npm run processCode` without any parameters. 132 | -------------------------------------------------------------------------------- /src/libRenameVars.ts: -------------------------------------------------------------------------------- 1 | import { generateRandomWords } from "./generateRandomWords.js"; 2 | import escodegen from "@javascript-obfuscator/escodegen"; 3 | import { namedTypes as n, builders as b, visit } from "ast-types"; 4 | import { astNodesAreEquivalent } from "ast-types"; 5 | import { camelCase } from "camel-case"; 6 | import type { Scope as ESLintScope } from "eslint"; 7 | import type { Scope, Variable } from "eslint-scope"; 8 | import { analyze } from "eslint-scope"; 9 | import MersenneTwister from "mersenne-twister"; 10 | import { pascalCase } from "pascal-case"; 11 | 12 | const iiiiiii = /(?:i|[^\sa-z0-9]){4,}$|_0x[a-zA-Z0-9]{6}/i; 13 | 14 | function getVarPrefix(type: ESLintScope.DefinitionType["type"]) { 15 | switch (type) { 16 | case "FunctionName": 17 | return "func"; 18 | case "Parameter": 19 | return "arg"; 20 | case "ClassName": 21 | return "Class"; 22 | case "ImportBinding": 23 | return "imported"; 24 | default: 25 | return "var"; 26 | } 27 | } 28 | 29 | const reservedWords = [ 30 | "arguments", 31 | "await", 32 | "break", 33 | "case", 34 | "catch", 35 | "class", 36 | "const", 37 | "continue", 38 | "debugger", 39 | "default", 40 | "delete", 41 | "do", 42 | "else", 43 | "enum", 44 | "export", 45 | "extends", 46 | "false", 47 | "finally", 48 | "for", 49 | "function", 50 | "get", 51 | "if", 52 | "import", 53 | "in", 54 | "instanceof", 55 | "new", 56 | "null", 57 | "return", 58 | "set", 59 | "super", 60 | "switch", 61 | "this", 62 | "throw", 63 | "true", 64 | "try", 65 | "typeof", 66 | "var", 67 | "void", 68 | "while", 69 | "with", 70 | "yield", 71 | ]; 72 | 73 | const getName = (name: string, testName: (name: string) => boolean) => { 74 | if (reservedWords.includes(name)) name = `_${name}`; 75 | 76 | for (let i = 0; i < 1e6; i++) { 77 | const newName = name + (i === 0 ? "" : i); 78 | 79 | i++; 80 | 81 | if (!testName(newName)) continue; 82 | 83 | return newName; 84 | } 85 | 86 | throw new Error("FAIL"); 87 | }; 88 | 89 | interface StaticScopeData { 90 | assignmentExpressions: n.AssignmentExpression[]; 91 | defineProperties: { 92 | /** 93 | * Object.defineProperty(exports, **"name"**, { get: function() { return getIdentifier; } }) 94 | */ 95 | name: string; 96 | /** 97 | * Object.defineProperty(exports, "name", { get: function() { return **getIdentifier;** } }) 98 | */ 99 | getIdentifier: n.Identifier; 100 | }[]; 101 | } 102 | 103 | function fetchStaticScopeData(scope: Scope) { 104 | const data: StaticScopeData = { 105 | assignmentExpressions: [], 106 | defineProperties: [], 107 | }; 108 | 109 | visit(scope.block, { 110 | visitIfStatement(path) { 111 | if ( 112 | n.UnaryExpression.check(path.node.test) && 113 | n.CallExpression.check(path.node.test.argument) && 114 | astNodesAreEquivalent( 115 | path.node.test.argument.callee, 116 | b.memberExpression( 117 | b.memberExpression( 118 | b.memberExpression( 119 | b.identifier("Object"), 120 | b.identifier("prototype") 121 | ), 122 | b.identifier("hasOwnProperty") 123 | ), 124 | b.identifier("call") 125 | ) 126 | ) && 127 | astNodesAreEquivalent( 128 | path.node.test.argument.arguments[0], 129 | b.identifier("exports") 130 | ) && 131 | n.Literal.check(path.node.test.argument.arguments[1]) && 132 | n.ExpressionStatement.check(path.node.consequent) && 133 | n.CallExpression.check(path.node.consequent.expression) && 134 | astNodesAreEquivalent( 135 | path.node.consequent.expression.callee, 136 | b.memberExpression( 137 | b.identifier("Object"), 138 | b.identifier("defineProperty") 139 | ) 140 | ) && 141 | astNodesAreEquivalent( 142 | path.node.consequent.expression.arguments[0], 143 | b.identifier("exports") 144 | ) && 145 | n.Literal.check(path.node.consequent.expression.arguments[1]) && 146 | n.ObjectExpression.check( 147 | path.node.consequent.expression.arguments[2] 148 | ) && 149 | n.Property.check( 150 | path.node.consequent.expression.arguments[2].properties[0] 151 | ) && 152 | n.FunctionExpression.check( 153 | path.node.consequent.expression.arguments[2].properties[0].value 154 | ) && 155 | n.ReturnStatement.check( 156 | path.node.consequent.expression.arguments[2].properties[0].value.body 157 | .body[0] 158 | ) && 159 | n.Identifier.check( 160 | path.node.consequent.expression.arguments[2].properties[0].value.body 161 | .body[0].argument 162 | ) 163 | ) 164 | data.defineProperties.push({ 165 | name: 166 | path.node.consequent.expression.arguments[1].value?.toString() || 167 | "", 168 | getIdentifier: 169 | path.node.consequent.expression.arguments[2].properties[0].value 170 | .body.body[0].argument, 171 | }); 172 | 173 | this.traverse(path); 174 | }, 175 | visitAssignmentExpression(path) { 176 | data.assignmentExpressions.push(path.node); 177 | 178 | this.traverse(path); 179 | }, 180 | }); 181 | 182 | return data; 183 | } 184 | 185 | function generateName( 186 | mt: MersenneTwister, 187 | scope: Scope, 188 | v: ESLintScope.Variable, 189 | sd: StaticScopeData 190 | ) { 191 | const def0 = v.defs[0]; 192 | const vars: Variable[] = []; 193 | 194 | let s: Scope | null = scope; 195 | while (s) { 196 | vars.push(...s.variables); 197 | s = s.upper; 198 | } 199 | 200 | let isClass = false; 201 | 202 | if (def0.type === "FunctionName" && def0.node.body.body.length === 0) 203 | return getName("noOp", (n) => !vars.some((s) => s.name === n)); 204 | 205 | let isFuncVar = false; 206 | 207 | if (def0.type === "Variable" && n.FunctionExpression.check(def0.node.init)) { 208 | isFuncVar = true; 209 | 210 | visit(def0.node.init.body, { 211 | visitThisExpression() { 212 | isClass = true; 213 | this.abort(); 214 | }, 215 | }); 216 | } 217 | 218 | if (def0.type === "FunctionName") 219 | visit(def0.node.body, { 220 | visitThisExpression() { 221 | isClass = true; 222 | this.abort(); 223 | }, 224 | }); 225 | 226 | for (const node of sd.defineProperties) { 227 | if (astNodesAreEquivalent(node.getIdentifier, b.identifier(v.name))) { 228 | // TODO: check if v.identifiers contains this identifier, otherwise the node may be a completely different variable 229 | 230 | return getName( 231 | (isClass ? pascalCase : camelCase)("e_" + node.name), 232 | (n) => !vars.some((s) => s.name === n) 233 | ); 234 | } 235 | } 236 | 237 | for (const node of sd.assignmentExpressions) { 238 | if ( 239 | n.MemberExpression.check(node.left) && 240 | n.Identifier.check(node.left.property) && 241 | !node.left.computed && 242 | astNodesAreEquivalent(node.right, b.identifier(v.name)) 243 | /*&& 244 | v.references.some( 245 | (i) => 246 | ((node.left as n.MemberExpression).property as n.Identifier) === 247 | i.identifier 248 | ) 249 | */ 250 | ) { 251 | // TODO: check if v.identifiers contains this identifier, otherwise the node may be a completely different variable 252 | return getName( 253 | (isClass ? pascalCase : camelCase)("m_" + node.left.property.name), 254 | (n) => !vars.some((s) => s.name === n) 255 | ); 256 | } else if ( 257 | astNodesAreEquivalent(node.left, b.identifier(v.name)) && 258 | n.ThisExpression.check(node.right) 259 | ) 260 | return getName("this", (n) => !vars.some((s) => s.name === n)); 261 | } 262 | 263 | const varPrefix = isClass 264 | ? "Class" 265 | : isFuncVar 266 | ? "func" 267 | : getVarPrefix(def0.type); 268 | 269 | if ( 270 | def0.type === "Variable" && 271 | n.CallExpression.check(def0.node.init) && 272 | astNodesAreEquivalent(def0.node.init.callee, b.identifier("require")) && 273 | n.Literal.check(def0.node.init.arguments[0]) && 274 | typeof def0.node.init.arguments[0].value === "string" 275 | ) 276 | return getName( 277 | camelCase("require" + def0.node.init.arguments[0].value), 278 | (n) => !vars.some((s) => s.name === n) 279 | ); 280 | else if ( 281 | def0.type === "Variable" && 282 | n.MemberExpression.check(def0.node.init) && 283 | n.Identifier.check(def0.node.init.property) 284 | ) 285 | return getName( 286 | "p_" + def0.node.init.property.name, 287 | (n) => !vars.some((s) => s.name === n) 288 | ); 289 | else if (def0.type === "Variable" && n.Identifier.check(def0.node.init)) 290 | return getName( 291 | "v_" + def0.node.init.name, 292 | (n) => !vars.some((s) => s.name === n) 293 | ); 294 | else if (def0.type === "Variable" && n.NewExpression.check(def0.node.init)) 295 | return getName( 296 | camelCase(escodegen.generate(def0.node.init.callee)), 297 | (n) => !vars.some((s) => s.name === n) 298 | ); 299 | else if (def0.type === "Variable" && n.ThisExpression.check(def0.node.init)) 300 | for (let i = 0; ; i++) { 301 | const newName = "_this" + (i === 0 ? "" : i); 302 | 303 | i++; 304 | 305 | if (vars.some((s) => s.name === newName)) continue; 306 | 307 | return newName; 308 | } 309 | 310 | while (true) { 311 | const newName = varPrefix + generateRandomWords(mt, 2).join(""); 312 | if (vars.some((s) => s.name === newName)) continue; 313 | return newName; 314 | } 315 | } 316 | 317 | export default function renameVars(program: n.Program, hash: number) { 318 | const mt = new MersenneTwister(hash); 319 | 320 | const scopeManger = analyze(program, { 321 | ecmaVersion: 6, 322 | sourceType: "module", 323 | }); 324 | 325 | // first def, new name 326 | const renamedNodes = new WeakMap(); 327 | const renamedNames = new Map(); 328 | 329 | for (const scope of scopeManger.scopes) { 330 | // takes an awful long time before JIT 331 | // but < 10 ms after 332 | const sd = fetchStaticScopeData(scope); 333 | 334 | for (const v of scope.variables) { 335 | if (!iiiiiii.test(v.name)) continue; 336 | 337 | const firstDef = v.defs[0]; 338 | 339 | const newName = 340 | renamedNodes.get(firstDef.node) || generateName(mt, scope, v, sd); 341 | 342 | renamedNames.set(v.name, newName); 343 | 344 | if (firstDef.type === "ClassName") 345 | renamedNodes.set(firstDef.node, newName); 346 | 347 | // used by generateName 348 | v.name = newName; 349 | 350 | for (const def of v.defs) def.name.name = newName; 351 | 352 | for (const ref of v.references) ref.identifier.name = newName; 353 | } 354 | 355 | // took the hack from the deobfuscator 356 | for (const ref of scope.references) { 357 | const got = renamedNames.get(ref.identifier.name); 358 | if (got) ref.identifier.name = got; 359 | } 360 | } 361 | 362 | const labels: string[] = []; 363 | 364 | // fix labels 365 | // eslint-scope doesn't have labels 366 | visit(program, { 367 | visitLabeledStatement(path) { 368 | while (true) { 369 | const newName = generateRandomWords(mt, 2).join(""); 370 | if (labels.includes(newName)) continue; 371 | labels.push(newName); 372 | 373 | visit(path.node, { 374 | visitContinueStatement(subPath) { 375 | if (subPath.node.label?.name === path.node.label.name) 376 | subPath.replace(b.continueStatement(b.identifier(newName))); 377 | return false; 378 | }, 379 | visitBreakStatement(subPath) { 380 | if (subPath.node.label?.name === path.node.label.name) 381 | subPath.replace(b.breakStatement(b.identifier(newName))); 382 | return false; 383 | }, 384 | }); 385 | 386 | path.replace(b.labeledStatement(b.identifier(newName), path.node.body)); 387 | this.traverse(path); 388 | return; 389 | } 390 | }, 391 | }); 392 | } 393 | -------------------------------------------------------------------------------- /src/libDecompile.ts: -------------------------------------------------------------------------------- 1 | import { isIdentifierName } from "@babel/helper-validator-identifier"; 2 | import escodegen from "@javascript-obfuscator/escodegen"; 3 | import { 4 | namedTypes as n, 5 | builders as b, 6 | visit, 7 | astNodesAreEquivalent, 8 | } from "ast-types"; 9 | 10 | // Expressions when the binary expression is doing the opposite 11 | const oppositeExpressions = { 12 | "==": "!=", 13 | "!=": "==", 14 | "===": "!==", 15 | "!==": "===", 16 | "<": ">=", 17 | ">": "<=", 18 | "<=": ">", 19 | ">=": "<", 20 | }; 21 | 22 | // Expressions when the binary expression is reversed 23 | const flippedExpressions = { 24 | "==": "!=", 25 | "!=": "==", 26 | "===": "!==", 27 | "!==": "===", 28 | "<": ">", 29 | ">": "<", 30 | "<=": ">=", 31 | ">=": "<=", 32 | }; 33 | 34 | export default function decompile(program: n.Program) { 35 | // return unminify.unminifySource(code, { safety: unminify.safetyLevels.SAFE }); 36 | 37 | /* 38 | RULE: 39 | 40 | replace(...body) will require doing 41 | 42 | this.visit(path.parentPath); 43 | this.traverse(path.parentPath); 44 | 45 | to catch everything 46 | 47 | otherwise replace(e) 48 | 49 | this.visit(path); 50 | this.traverse(path); 51 | 52 | */ 53 | 54 | visit(program, { 55 | // String.fromCharCode(1, 2, 3, 4).toLowerCase() 56 | visitCallExpression(path) { 57 | if ( 58 | n.MemberExpression.check(path.node.callee) && 59 | n.CallExpression.check(path.node.callee.object) && 60 | astNodesAreEquivalent( 61 | path.node.callee.object.callee, 62 | b.memberExpression( 63 | b.identifier("String"), 64 | b.identifier("fromCharCode") 65 | ) 66 | ) && 67 | path.node.callee.object.arguments.every( 68 | (arg) => n.Literal.check(arg) && typeof arg.value === "number" 69 | ) && 70 | astNodesAreEquivalent( 71 | b.identifier("toLowerCase"), 72 | path.node.callee.property 73 | ) 74 | ) { 75 | path.replace( 76 | b.literal( 77 | String.fromCharCode( 78 | ...path.node.callee.object.arguments.map( 79 | (arg) => (arg as n.Literal).value as number 80 | ) 81 | ).toLowerCase() 82 | ) 83 | ); 84 | 85 | return false; 86 | } else this.traverse(path); 87 | }, 88 | visitVariableDeclaration(path) { 89 | if ( 90 | path.node.declarations.length !== 1 && 91 | !n.ForStatement.check(path.parent?.value) 92 | ) { 93 | path.replace( 94 | ...path.node.declarations.map((declaration) => 95 | b.variableDeclaration(path.node.kind, [declaration]) 96 | ) 97 | ); 98 | 99 | this.visit(path.parentPath); 100 | this.traverse(path.parentPath); 101 | return; 102 | } 103 | 104 | this.traverse(path); 105 | }, 106 | visitReturnStatement(path) { 107 | if (n.SequenceExpression.check(path.node.argument)) { 108 | const [realReturn] = path.node.argument.expressions.slice(-1); 109 | const exps = path.node.argument.expressions.slice(0, -1); 110 | 111 | const body = [ 112 | ...exps.map((e) => b.expressionStatement(e)), 113 | b.returnStatement(realReturn), 114 | ]; 115 | 116 | if (path.parent.node?.type === "IfStatement") 117 | path.replace(b.blockStatement(body)); 118 | else if ( 119 | ["Program", "BlockStatement", "SwitchCase"].includes( 120 | path.parent.node?.type 121 | ) 122 | ) { 123 | path.replace(...body); 124 | this.visit(path.parentPath); 125 | this.traverse(path.parentPath); 126 | return; 127 | } else throw new Error(`Unsupported parent ${path.parent.node?.type}`); 128 | } 129 | 130 | this.traverse(path); 131 | }, 132 | visitEmptyStatement(path) { 133 | if ( 134 | (!n.ForStatement.check(path.parent?.value) || path.name !== "body") && 135 | !n.IfStatement.check(path.parent?.value) && 136 | !n.SwitchStatement.check(path.parent?.value) && 137 | !n.WhileStatement.check(path.parent?.value) 138 | ) { 139 | path.replace(); 140 | } 141 | return false; 142 | }, 143 | visitExpressionStatement(path) { 144 | // condition (?) expression as an expression is usually a substitude for if(){}else{} 145 | if (n.ConditionalExpression.check(path.node.expression)) { 146 | path.replace( 147 | b.ifStatement( 148 | path.node.expression.test, 149 | b.expressionStatement(path.node.expression.consequent), 150 | b.expressionStatement(path.node.expression.alternate) 151 | ) 152 | ); 153 | 154 | this.visit(path); 155 | this.traverse(path); 156 | 157 | return false; 158 | } 159 | 160 | if (n.SequenceExpression.check(path.node.expression)) { 161 | const body = path.node.expression.expressions.map((e) => 162 | b.expressionStatement(e) 163 | ); 164 | 165 | // global or in block 166 | if ( 167 | !path.parent?.node.type || 168 | ["Program", "BlockStatement", "SwitchCase"].includes( 169 | path.parent.node.type 170 | ) 171 | ) { 172 | path.replace(...body); 173 | this.visit(path.parentPath); 174 | this.traverse(path.parentPath); 175 | return; 176 | } else path.replace(b.blockStatement(body)); 177 | 178 | return this.traverse(path); 179 | } 180 | 181 | if (n.LogicalExpression.check(path.node.expression)) { 182 | if (path.node.expression.operator === "&&") { 183 | // it's safe to assume the right operator is probably a sequence/one thing 184 | path.replace( 185 | b.ifStatement( 186 | path.node.expression.left, 187 | b.expressionStatement(path.node.expression.right) 188 | ) 189 | ); 190 | 191 | this.visit(path); 192 | this.traverse(path); 193 | 194 | return false; 195 | } 196 | 197 | if ( 198 | path.node.expression.operator === "||" && 199 | n.BinaryExpression.check(path.node.expression.left) && 200 | path.node.expression.left.operator in oppositeExpressions 201 | ) { 202 | // so far: || has been used exclusively with binary expressions to check the opposite, if it's null then do nothing 203 | // other||wise, execute expression.right 204 | path.replace( 205 | b.ifStatement( 206 | b.binaryExpression( 207 | oppositeExpressions[ 208 | path.node.expression.left 209 | .operator as keyof typeof oppositeExpressions 210 | ] as Parameters[0], 211 | path.node.expression.left.left, 212 | path.node.expression.left.right 213 | ), 214 | b.expressionStatement(path.node.expression.right) 215 | ) 216 | ); 217 | 218 | this.visit(path); 219 | this.traverse(path); 220 | 221 | return false; 222 | } 223 | } 224 | 225 | if ( 226 | n.LogicalExpression.check(path.node.expression) && 227 | path.node.expression.operator === "||" 228 | ) { 229 | // it's safe to assume the right operator is probably a sequence/one thing 230 | path.replace( 231 | b.ifStatement( 232 | b.unaryExpression("!", path.node.expression.left), 233 | b.expressionStatement(path.node.expression.right) 234 | ) 235 | ); 236 | 237 | this.visit(path); 238 | this.traverse(path); 239 | 240 | return false; 241 | } 242 | 243 | this.traverse(path); 244 | }, 245 | visitUnaryExpression(path) { 246 | if (path.node.operator === "!") 247 | if ( 248 | n.Literal.check(path.node.argument) && 249 | typeof path.node.argument.value === "number" 250 | ) 251 | return path.replace(b.literal(!path.node.argument.value)), false; 252 | else if (n.ArrayExpression.check(path.node.argument)) 253 | return path.replace(b.literal(false)), false; 254 | else if ( 255 | n.UnaryExpression.check(path.node.argument) && 256 | n.ArrayExpression.check(path.node.argument.argument) 257 | ) 258 | return path.replace(b.literal(true)), false; 259 | 260 | this.traverse(path); 261 | }, 262 | visitBinaryExpression(path) { 263 | // traverse and simplify the operators before doing anything 264 | // this.traverse(path); 265 | 266 | // right side should always be simple 267 | // simple: typeof a === "string" 268 | // not simple: 12 === a 269 | // not simple: -1 !== test 270 | 271 | const isSimple = (node: n.Node) => 272 | n.Literal.check(node) || 273 | (n.UnaryExpression.check(node) && n.Literal.check(node.argument)); 274 | 275 | if ( 276 | path.node.operator in flippedExpressions && 277 | isSimple(path.node.left) && 278 | !isSimple(path.node.right) 279 | ) { 280 | // flip 281 | path.replace( 282 | b.binaryExpression( 283 | flippedExpressions[ 284 | path.node.operator as keyof typeof flippedExpressions 285 | ] as Parameters[0], 286 | path.node.right, 287 | path.node.left 288 | ) 289 | ); 290 | } 291 | 292 | this.traverse(path); 293 | }, 294 | visitForStatement(path) { 295 | // console.log("got a for statement", escodegen.generate(path.node)); 296 | 297 | if ( 298 | n.VariableDeclaration.check(path.node.init) && 299 | path.node.init.declarations.length !== 1 && 300 | path.node.init.kind === "var" && // this is a var-only optimization 301 | path.parent?.node.type !== "LabeledStatement" // too much work/imopssible 302 | ) { 303 | // move all the ones before the final declaration outside of the statement 304 | const [realDeclaration] = path.node.init.declarations.slice(-1); 305 | const declarations = path.node.init.declarations.slice(0, -1); 306 | 307 | const { kind } = path.node.init; 308 | 309 | const body = [ 310 | ...declarations.map((declaration) => 311 | b.variableDeclaration(kind, [declaration]) 312 | ), 313 | b.forStatement( 314 | b.variableDeclaration(kind, [realDeclaration]), 315 | path.node.test, 316 | path.node.update, 317 | path.node.body 318 | ), 319 | ]; 320 | 321 | if ( 322 | !path.parent?.node.type || 323 | ["Program", "BlockStatement", "SwitchCase"].includes( 324 | path.parent.node.type 325 | ) 326 | ) { 327 | // global or in block 328 | path.replace(...body); 329 | this.visit(path.parentPath); 330 | this.traverse(path.parentPath); 331 | return; 332 | } else path.replace(b.blockStatement(body)); 333 | } 334 | 335 | this.traverse(path); 336 | }, 337 | visitIfStatement(path) { 338 | // if((optimized, false))... 339 | if (n.SequenceExpression.check(path.node.test)) { 340 | const [realTest] = path.node.test.expressions.slice(-1); 341 | 342 | const body = [ 343 | ...path.node.test.expressions 344 | .slice(0, -1) 345 | .map((e) => b.expressionStatement(e)), 346 | b.ifStatement(realTest, path.node.consequent, path.node.alternate), 347 | ]; 348 | 349 | if ( 350 | !path.parent?.node.type || 351 | ["Program", "BlockStatement", "SwitchCase"].includes( 352 | path.parent.node.type 353 | ) 354 | ) { 355 | // global or in block 356 | path.replace(...body); 357 | this.visit(path.parentPath); 358 | this.traverse(path.parentPath); 359 | return; 360 | } else path.replace(b.blockStatement(body)); 361 | } 362 | 363 | if (n.VariableDeclaration.check(path.node.consequent)) 364 | path.replace( 365 | b.ifStatement( 366 | path.node.test, 367 | n.VariableDeclaration.check(path.node.consequent) 368 | ? b.blockStatement([path.node.consequent]) 369 | : path.node.consequent, 370 | path.node.alternate 371 | ) 372 | ); 373 | 374 | if (n.VariableDeclaration.check(path.node.alternate)) 375 | path.replace( 376 | b.ifStatement( 377 | path.node.test, 378 | path.node.consequent, 379 | n.VariableDeclaration.check(path.node.alternate) 380 | ? b.blockStatement([path.node.alternate]) 381 | : path.node.alternate 382 | ) 383 | ); 384 | 385 | this.traverse(path); 386 | }, 387 | visitMemberExpression(path) { 388 | if ( 389 | path.node.computed && 390 | n.Literal.check(path.node.property) && 391 | typeof path.node.property.value === "string" && 392 | isIdentifierName(path.node.property.value) 393 | ) 394 | path.replace( 395 | b.memberExpression( 396 | path.node.object, 397 | b.identifier(path.node.property.value), 398 | false 399 | ) 400 | ); 401 | 402 | this.traverse(path); 403 | }, 404 | visitProperty(path) { 405 | if ( 406 | n.Literal.check(path.node.key) && 407 | typeof path.node.key.value === "string" && 408 | isIdentifierName(path.node.key.value) 409 | ) 410 | path.replace( 411 | b.property( 412 | path.node.kind, 413 | b.identifier(path.node.key.value), 414 | path.node.value 415 | ) 416 | ); 417 | 418 | this.traverse(path); 419 | }, 420 | visitMethodDefinition(path) { 421 | if ( 422 | n.Literal.check(path.node.key) && 423 | typeof path.node.key.value === "string" && 424 | isIdentifierName(path.node.key.value) 425 | ) 426 | path.replace( 427 | b.methodDefinition( 428 | path.node.kind, 429 | b.identifier(path.node.key.value), 430 | path.node.value, 431 | path.node.static 432 | ) 433 | ); 434 | 435 | this.traverse(path); 436 | }, 437 | }); 438 | 439 | return escodegen.generate(program); 440 | } 441 | -------------------------------------------------------------------------------- /src/generateRandomWords.ts: -------------------------------------------------------------------------------- 1 | import type MersenneTwister from "mersenne-twister"; 2 | 3 | const wordList = [ 4 | "ability", 5 | "able", 6 | "aboard", 7 | "about", 8 | "above", 9 | "accept", 10 | "accident", 11 | "according", 12 | "account", 13 | "accurate", 14 | "acres", 15 | "across", 16 | "act", 17 | "action", 18 | "active", 19 | "activity", 20 | "actual", 21 | "actually", 22 | "add", 23 | "addition", 24 | "additional", 25 | "adjective", 26 | "adult", 27 | "adventure", 28 | "advice", 29 | "affect", 30 | "afraid", 31 | "after", 32 | "afternoon", 33 | "again", 34 | "against", 35 | "age", 36 | "ago", 37 | "agree", 38 | "ahead", 39 | "aid", 40 | "air", 41 | "airplane", 42 | "alike", 43 | "alive", 44 | "all", 45 | "allow", 46 | "almost", 47 | "alone", 48 | "along", 49 | "aloud", 50 | "alphabet", 51 | "already", 52 | "also", 53 | "although", 54 | "am", 55 | "among", 56 | "amount", 57 | "ancient", 58 | "angle", 59 | "angry", 60 | "animal", 61 | "announced", 62 | "another", 63 | "answer", 64 | "ants", 65 | "any", 66 | "anybody", 67 | "anyone", 68 | "anything", 69 | "anyway", 70 | "anywhere", 71 | "apart", 72 | "apartment", 73 | "appearance", 74 | "apple", 75 | "applied", 76 | "appropriate", 77 | "are", 78 | "area", 79 | "arm", 80 | "army", 81 | "around", 82 | "arrange", 83 | "arrangement", 84 | "arrive", 85 | "arrow", 86 | "art", 87 | "article", 88 | "as", 89 | "aside", 90 | "ask", 91 | "asleep", 92 | "at", 93 | "ate", 94 | "atmosphere", 95 | "atom", 96 | "atomic", 97 | "attached", 98 | "attack", 99 | "attempt", 100 | "attention", 101 | "audience", 102 | "author", 103 | "automobile", 104 | "available", 105 | "average", 106 | "avoid", 107 | "aware", 108 | "away", 109 | "baby", 110 | "back", 111 | "bad", 112 | "badly", 113 | "bag", 114 | "balance", 115 | "ball", 116 | "balloon", 117 | "band", 118 | "bank", 119 | "bar", 120 | "bare", 121 | "bark", 122 | "barn", 123 | "base", 124 | "baseball", 125 | "basic", 126 | "basis", 127 | "basket", 128 | "bat", 129 | "battle", 130 | "be", 131 | "bean", 132 | "bear", 133 | "beat", 134 | "beautiful", 135 | "beauty", 136 | "became", 137 | "because", 138 | "become", 139 | "becoming", 140 | "bee", 141 | "been", 142 | "before", 143 | "began", 144 | "beginning", 145 | "begun", 146 | "behavior", 147 | "behind", 148 | "being", 149 | "believed", 150 | "bell", 151 | "belong", 152 | "below", 153 | "belt", 154 | "bend", 155 | "beneath", 156 | "bent", 157 | "beside", 158 | "best", 159 | "bet", 160 | "better", 161 | "between", 162 | "beyond", 163 | "bicycle", 164 | "bigger", 165 | "biggest", 166 | "bill", 167 | "birds", 168 | "birth", 169 | "birthday", 170 | "bit", 171 | "bite", 172 | "black", 173 | "blank", 174 | "blanket", 175 | "blew", 176 | "blind", 177 | "block", 178 | "blood", 179 | "blow", 180 | "blue", 181 | "board", 182 | "boat", 183 | "body", 184 | "bone", 185 | "book", 186 | "border", 187 | "born", 188 | "both", 189 | "bottle", 190 | "bottom", 191 | "bound", 192 | "bow", 193 | "bowl", 194 | "box", 195 | "boy", 196 | "brain", 197 | "branch", 198 | "brass", 199 | "brave", 200 | "bread", 201 | "break", 202 | "breakfast", 203 | "breath", 204 | "breathe", 205 | "breathing", 206 | "breeze", 207 | "brick", 208 | "bridge", 209 | "brief", 210 | "bright", 211 | "bring", 212 | "broad", 213 | "broke", 214 | "broken", 215 | "brother", 216 | "brought", 217 | "brown", 218 | "brush", 219 | "buffalo", 220 | "build", 221 | "building", 222 | "built", 223 | "buried", 224 | "burn", 225 | "burst", 226 | "bus", 227 | "bush", 228 | "business", 229 | "busy", 230 | "but", 231 | "butter", 232 | "buy", 233 | "by", 234 | "cabin", 235 | "cage", 236 | "cake", 237 | "call", 238 | "calm", 239 | "came", 240 | "camera", 241 | "camp", 242 | "can", 243 | "canal", 244 | "cannot", 245 | "cap", 246 | "capital", 247 | "captain", 248 | "captured", 249 | "car", 250 | "carbon", 251 | "card", 252 | "care", 253 | "careful", 254 | "carefully", 255 | "carried", 256 | "carry", 257 | "case", 258 | "cast", 259 | "castle", 260 | "cat", 261 | "catch", 262 | "cattle", 263 | "caught", 264 | "cause", 265 | "cave", 266 | "cell", 267 | "cent", 268 | "center", 269 | "central", 270 | "century", 271 | "certain", 272 | "certainly", 273 | "chain", 274 | "chair", 275 | "chamber", 276 | "chance", 277 | "change", 278 | "changing", 279 | "chapter", 280 | "character", 281 | "characteristic", 282 | "charge", 283 | "chart", 284 | "check", 285 | "cheese", 286 | "chemical", 287 | "chest", 288 | "chicken", 289 | "chief", 290 | "child", 291 | "children", 292 | "choice", 293 | "choose", 294 | "chose", 295 | "chosen", 296 | "church", 297 | "circle", 298 | "circus", 299 | "citizen", 300 | "city", 301 | "class", 302 | "classroom", 303 | "claws", 304 | "clay", 305 | "clean", 306 | "clear", 307 | "clearly", 308 | "climate", 309 | "climb", 310 | "clock", 311 | "close", 312 | "closely", 313 | "closer", 314 | "cloth", 315 | "clothes", 316 | "clothing", 317 | "cloud", 318 | "club", 319 | "coach", 320 | "coal", 321 | "coast", 322 | "coat", 323 | "coffee", 324 | "cold", 325 | "collect", 326 | "college", 327 | "colony", 328 | "color", 329 | "column", 330 | "combination", 331 | "combine", 332 | "come", 333 | "comfortable", 334 | "coming", 335 | "command", 336 | "common", 337 | "community", 338 | "company", 339 | "compare", 340 | "compass", 341 | "complete", 342 | "completely", 343 | "complex", 344 | "composed", 345 | "composition", 346 | "compound", 347 | "concerned", 348 | "condition", 349 | "congress", 350 | "connected", 351 | "consider", 352 | "consist", 353 | "consonant", 354 | "constantly", 355 | "construction", 356 | "contain", 357 | "continent", 358 | "continued", 359 | "contrast", 360 | "control", 361 | "conversation", 362 | "cook", 363 | "cookies", 364 | "cool", 365 | "copper", 366 | "copy", 367 | "corn", 368 | "corner", 369 | "correct", 370 | "correctly", 371 | "cost", 372 | "cotton", 373 | "could", 374 | "count", 375 | "country", 376 | "couple", 377 | "courage", 378 | "course", 379 | "court", 380 | "cover", 381 | "cow", 382 | "cowboy", 383 | "crack", 384 | "cream", 385 | "create", 386 | "creature", 387 | "crew", 388 | "crop", 389 | "cross", 390 | "crowd", 391 | "cry", 392 | "cup", 393 | "curious", 394 | "current", 395 | "curve", 396 | "customs", 397 | "cut", 398 | "cutting", 399 | "daily", 400 | "damage", 401 | "dance", 402 | "danger", 403 | "dangerous", 404 | "dark", 405 | "darkness", 406 | "date", 407 | "daughter", 408 | "dawn", 409 | "day", 410 | "dead", 411 | "deal", 412 | "dear", 413 | "death", 414 | "decide", 415 | "declared", 416 | "deep", 417 | "deeply", 418 | "deer", 419 | "definition", 420 | "degree", 421 | "depend", 422 | "depth", 423 | "describe", 424 | "desert", 425 | "design", 426 | "desk", 427 | "detail", 428 | "determine", 429 | "develop", 430 | "development", 431 | "diagram", 432 | "diameter", 433 | "did", 434 | "die", 435 | "differ", 436 | "difference", 437 | "different", 438 | "difficult", 439 | "difficulty", 440 | "dig", 441 | "dinner", 442 | "direct", 443 | "direction", 444 | "directly", 445 | "dirt", 446 | "dirty", 447 | "disappear", 448 | "discover", 449 | "discovery", 450 | "discuss", 451 | "discussion", 452 | "disease", 453 | "dish", 454 | "distance", 455 | "distant", 456 | "divide", 457 | "division", 458 | "do", 459 | "doctor", 460 | "does", 461 | "dog", 462 | "doing", 463 | "doll", 464 | "dollar", 465 | "done", 466 | "donkey", 467 | "door", 468 | "dot", 469 | "double", 470 | "doubt", 471 | "down", 472 | "dozen", 473 | "draw", 474 | "drawn", 475 | "dream", 476 | "dress", 477 | "drew", 478 | "dried", 479 | "drink", 480 | "drive", 481 | "driven", 482 | "driver", 483 | "driving", 484 | "drop", 485 | "dropped", 486 | "drove", 487 | "dry", 488 | "duck", 489 | "due", 490 | "dug", 491 | "dull", 492 | "during", 493 | "dust", 494 | "duty", 495 | "each", 496 | "eager", 497 | "ear", 498 | "earlier", 499 | "early", 500 | "earn", 501 | "earth", 502 | "easier", 503 | "easily", 504 | "east", 505 | "easy", 506 | "eat", 507 | "eaten", 508 | "edge", 509 | "education", 510 | "effect", 511 | "effort", 512 | "egg", 513 | "eight", 514 | "either", 515 | "electric", 516 | "electricity", 517 | "element", 518 | "elephant", 519 | "eleven", 520 | "else", 521 | "empty", 522 | "end", 523 | "enemy", 524 | "energy", 525 | "engine", 526 | "engineer", 527 | "enjoy", 528 | "enough", 529 | "enter", 530 | "entire", 531 | "entirely", 532 | "environment", 533 | "equal", 534 | "equally", 535 | "equator", 536 | "equipment", 537 | "escape", 538 | "especially", 539 | "essential", 540 | "establish", 541 | "even", 542 | "evening", 543 | "event", 544 | "eventually", 545 | "ever", 546 | "every", 547 | "everybody", 548 | "everyone", 549 | "everything", 550 | "everywhere", 551 | "evidence", 552 | "exact", 553 | "exactly", 554 | "examine", 555 | "example", 556 | "excellent", 557 | "except", 558 | "exchange", 559 | "excited", 560 | "excitement", 561 | "exciting", 562 | "exclaimed", 563 | "exercise", 564 | "exist", 565 | "expect", 566 | "experience", 567 | "experiment", 568 | "explain", 569 | "explanation", 570 | "explore", 571 | "express", 572 | "expression", 573 | "extra", 574 | "eye", 575 | "face", 576 | "facing", 577 | "fact", 578 | "factor", 579 | "factory", 580 | "failed", 581 | "fair", 582 | "fairly", 583 | "fall", 584 | "fallen", 585 | "familiar", 586 | "family", 587 | "famous", 588 | "far", 589 | "farm", 590 | "farmer", 591 | "farther", 592 | "fast", 593 | "fastened", 594 | "faster", 595 | "fat", 596 | "father", 597 | "favorite", 598 | "fear", 599 | "feathers", 600 | "feature", 601 | "fed", 602 | "feed", 603 | "feel", 604 | "feet", 605 | "fell", 606 | "fellow", 607 | "felt", 608 | "fence", 609 | "few", 610 | "fewer", 611 | "field", 612 | "fierce", 613 | "fifteen", 614 | "fifth", 615 | "fifty", 616 | "fight", 617 | "fighting", 618 | "figure", 619 | "fill", 620 | "film", 621 | "final", 622 | "finally", 623 | "find", 624 | "fine", 625 | "finest", 626 | "finger", 627 | "finish", 628 | "fire", 629 | "fireplace", 630 | "firm", 631 | "first", 632 | "fish", 633 | "five", 634 | "fix", 635 | "flag", 636 | "flame", 637 | "flat", 638 | "flew", 639 | "flies", 640 | "flight", 641 | "floating", 642 | "floor", 643 | "flow", 644 | "flower", 645 | "fly", 646 | "fog", 647 | "folks", 648 | "follow", 649 | "food", 650 | "foot", 651 | "football", 652 | "for", 653 | "force", 654 | "foreign", 655 | "forest", 656 | "forget", 657 | "forgot", 658 | "forgotten", 659 | "form", 660 | "former", 661 | "fort", 662 | "forth", 663 | "forty", 664 | "forward", 665 | "fought", 666 | "found", 667 | "four", 668 | "fourth", 669 | "fox", 670 | "frame", 671 | "free", 672 | "freedom", 673 | "frequently", 674 | "fresh", 675 | "friend", 676 | "friendly", 677 | "frighten", 678 | "frog", 679 | "from", 680 | "front", 681 | "frozen", 682 | "fruit", 683 | "fuel", 684 | "full", 685 | "fully", 686 | "fun", 687 | "function", 688 | "funny", 689 | "fur", 690 | "furniture", 691 | "further", 692 | "future", 693 | "gain", 694 | "game", 695 | "garage", 696 | "garden", 697 | "gas", 698 | "gasoline", 699 | "gate", 700 | "gather", 701 | "gave", 702 | "general", 703 | "generally", 704 | "gentle", 705 | "gently", 706 | "get", 707 | "getting", 708 | "giant", 709 | "gift", 710 | "girl", 711 | "give", 712 | "given", 713 | "giving", 714 | "glad", 715 | "glass", 716 | "globe", 717 | "go", 718 | "goes", 719 | "gold", 720 | "golden", 721 | "gone", 722 | "good", 723 | "goose", 724 | "got", 725 | "government", 726 | "grabbed", 727 | "grade", 728 | "gradually", 729 | "grain", 730 | "grandfather", 731 | "grandmother", 732 | "graph", 733 | "grass", 734 | "gravity", 735 | "gray", 736 | "great", 737 | "greater", 738 | "greatest", 739 | "greatly", 740 | "green", 741 | "grew", 742 | "ground", 743 | "group", 744 | "grow", 745 | "grown", 746 | "growth", 747 | "guard", 748 | "guess", 749 | "guide", 750 | "gulf", 751 | "gun", 752 | "habit", 753 | "had", 754 | "hair", 755 | "half", 756 | "halfway", 757 | "hall", 758 | "hand", 759 | "handle", 760 | "handsome", 761 | "hang", 762 | "happen", 763 | "happened", 764 | "happily", 765 | "happy", 766 | "harbor", 767 | "hard", 768 | "harder", 769 | "hardly", 770 | "has", 771 | "hat", 772 | "have", 773 | "having", 774 | "hay", 775 | "he", 776 | "headed", 777 | "heading", 778 | "health", 779 | "heard", 780 | "hearing", 781 | "heart", 782 | "heat", 783 | "heavy", 784 | "height", 785 | "held", 786 | "hello", 787 | "help", 788 | "helpful", 789 | "her", 790 | "herd", 791 | "here", 792 | "herself", 793 | "hidden", 794 | "hide", 795 | "high", 796 | "higher", 797 | "highest", 798 | "highway", 799 | "hill", 800 | "him", 801 | "himself", 802 | "his", 803 | "history", 804 | "hit", 805 | "hold", 806 | "hole", 807 | "hollow", 808 | "home", 809 | "honor", 810 | "hope", 811 | "horn", 812 | "horse", 813 | "hospital", 814 | "hot", 815 | "hour", 816 | "house", 817 | "how", 818 | "however", 819 | "huge", 820 | "human", 821 | "hundred", 822 | "hung", 823 | "hungry", 824 | "hunt", 825 | "hunter", 826 | "hurried", 827 | "hurry", 828 | "hurt", 829 | "husband", 830 | "ice", 831 | "idea", 832 | "identity", 833 | "if", 834 | "ill", 835 | "image", 836 | "imagine", 837 | "immediately", 838 | "importance", 839 | "important", 840 | "impossible", 841 | "improve", 842 | "in", 843 | "inch", 844 | "include", 845 | "including", 846 | "income", 847 | "increase", 848 | "indeed", 849 | "independent", 850 | "indicate", 851 | "individual", 852 | "industrial", 853 | "industry", 854 | "influence", 855 | "information", 856 | "inside", 857 | "instance", 858 | "instant", 859 | "instead", 860 | "instrument", 861 | "interest", 862 | "interior", 863 | "into", 864 | "introduced", 865 | "invented", 866 | "involved", 867 | "iron", 868 | "is", 869 | "island", 870 | "it", 871 | "its", 872 | "itself", 873 | "jack", 874 | "jar", 875 | "jet", 876 | "job", 877 | "join", 878 | "joined", 879 | "journey", 880 | "joy", 881 | "judge", 882 | "jump", 883 | "jungle", 884 | "just", 885 | "keep", 886 | "kept", 887 | "key", 888 | "kids", 889 | "kill", 890 | "kind", 891 | "kitchen", 892 | "knew", 893 | "knife", 894 | "know", 895 | "knowledge", 896 | "known", 897 | "label", 898 | "labor", 899 | "lack", 900 | "lady", 901 | "laid", 902 | "lake", 903 | "lamp", 904 | "land", 905 | "language", 906 | "large", 907 | "larger", 908 | "largest", 909 | "last", 910 | "late", 911 | "later", 912 | "laugh", 913 | "law", 914 | "lay", 915 | "layers", 916 | "lead", 917 | "leader", 918 | "leaf", 919 | "learn", 920 | "least", 921 | "leather", 922 | "leave", 923 | "leaving", 924 | "led", 925 | "left", 926 | "leg", 927 | "length", 928 | "lesson", 929 | "let", 930 | "letter", 931 | "level", 932 | "library", 933 | "lie", 934 | "life", 935 | "lift", 936 | "light", 937 | "like", 938 | "likely", 939 | "limited", 940 | "line", 941 | "lion", 942 | "lips", 943 | "liquid", 944 | "list", 945 | "listen", 946 | "little", 947 | "live", 948 | "living", 949 | "load", 950 | "local", 951 | "locate", 952 | "location", 953 | "log", 954 | "lonely", 955 | "long", 956 | "longer", 957 | "look", 958 | "loose", 959 | "lose", 960 | "loss", 961 | "lost", 962 | "lot", 963 | "loud", 964 | "love", 965 | "lovely", 966 | "low", 967 | "lower", 968 | "luck", 969 | "lucky", 970 | "lunch", 971 | "lungs", 972 | "lying", 973 | "machine", 974 | "machinery", 975 | "mad", 976 | "made", 977 | "magic", 978 | "magnet", 979 | "mail", 980 | "main", 981 | "mainly", 982 | "major", 983 | "make", 984 | "making", 985 | "man", 986 | "managed", 987 | "manner", 988 | "manufacturing", 989 | "many", 990 | "map", 991 | "mark", 992 | "market", 993 | "married", 994 | "mass", 995 | "massage", 996 | "master", 997 | "material", 998 | "mathematics", 999 | "matter", 1000 | "may", 1001 | "maybe", 1002 | "me", 1003 | "meal", 1004 | "mean", 1005 | "means", 1006 | "meant", 1007 | "measure", 1008 | "meat", 1009 | "medicine", 1010 | "meet", 1011 | "melted", 1012 | "member", 1013 | "memory", 1014 | "men", 1015 | "mental", 1016 | "merely", 1017 | "met", 1018 | "metal", 1019 | "method", 1020 | "mice", 1021 | "middle", 1022 | "might", 1023 | "mighty", 1024 | "mile", 1025 | "military", 1026 | "milk", 1027 | "mill", 1028 | "mind", 1029 | "mine", 1030 | "minerals", 1031 | "minute", 1032 | "mirror", 1033 | "missing", 1034 | "mission", 1035 | "mistake", 1036 | "mix", 1037 | "mixture", 1038 | "model", 1039 | "modern", 1040 | "molecular", 1041 | "moment", 1042 | "money", 1043 | "monkey", 1044 | "month", 1045 | "mood", 1046 | "moon", 1047 | "more", 1048 | "morning", 1049 | "most", 1050 | "mostly", 1051 | "mother", 1052 | "motion", 1053 | "motor", 1054 | "mountain", 1055 | "mouse", 1056 | "mouth", 1057 | "move", 1058 | "movement", 1059 | "movie", 1060 | "moving", 1061 | "mud", 1062 | "muscle", 1063 | "music", 1064 | "musical", 1065 | "must", 1066 | "my", 1067 | "myself", 1068 | "mysterious", 1069 | "nails", 1070 | "name", 1071 | "nation", 1072 | "national", 1073 | "native", 1074 | "natural", 1075 | "naturally", 1076 | "nature", 1077 | "near", 1078 | "nearby", 1079 | "nearer", 1080 | "nearest", 1081 | "nearly", 1082 | "necessary", 1083 | "neck", 1084 | "needed", 1085 | "needle", 1086 | "needs", 1087 | "negative", 1088 | "neighbor", 1089 | "neighborhood", 1090 | "nervous", 1091 | "nest", 1092 | "never", 1093 | "new", 1094 | "news", 1095 | "newspaper", 1096 | "next", 1097 | "nice", 1098 | "night", 1099 | "nine", 1100 | "no", 1101 | "nobody", 1102 | "nodded", 1103 | "noise", 1104 | "none", 1105 | "noon", 1106 | "nor", 1107 | "north", 1108 | "nose", 1109 | "not", 1110 | "note", 1111 | "noted", 1112 | "nothing", 1113 | "notice", 1114 | "noun", 1115 | "now", 1116 | "number", 1117 | "numeral", 1118 | "nuts", 1119 | "object", 1120 | "observe", 1121 | "obtain", 1122 | "occasionally", 1123 | "occur", 1124 | "ocean", 1125 | "of", 1126 | "off", 1127 | "offer", 1128 | "office", 1129 | "officer", 1130 | "official", 1131 | "oil", 1132 | "old", 1133 | "older", 1134 | "oldest", 1135 | "on", 1136 | "once", 1137 | "one", 1138 | "only", 1139 | "onto", 1140 | "open", 1141 | "operation", 1142 | "opinion", 1143 | "opportunity", 1144 | "opposite", 1145 | "or", 1146 | "orange", 1147 | "orbit", 1148 | "order", 1149 | "ordinary", 1150 | "organization", 1151 | "organized", 1152 | "origin", 1153 | "original", 1154 | "other", 1155 | "ought", 1156 | "our", 1157 | "ourselves", 1158 | "out", 1159 | "outer", 1160 | "outline", 1161 | "outside", 1162 | "over", 1163 | "own", 1164 | "owner", 1165 | "oxygen", 1166 | "pack", 1167 | "package", 1168 | "page", 1169 | "paid", 1170 | "pain", 1171 | "paint", 1172 | "pair", 1173 | "palace", 1174 | "pale", 1175 | "pan", 1176 | "paper", 1177 | "paragraph", 1178 | "parallel", 1179 | "parent", 1180 | "park", 1181 | "part", 1182 | "particles", 1183 | "particular", 1184 | "particularly", 1185 | "partly", 1186 | "parts", 1187 | "party", 1188 | "pass", 1189 | "passage", 1190 | "past", 1191 | "path", 1192 | "pattern", 1193 | "pay", 1194 | "peace", 1195 | "pen", 1196 | "pencil", 1197 | "people", 1198 | "per", 1199 | "percent", 1200 | "perfect", 1201 | "perfectly", 1202 | "perhaps", 1203 | "period", 1204 | "person", 1205 | "personal", 1206 | "pet", 1207 | "phrase", 1208 | "physical", 1209 | "piano", 1210 | "pick", 1211 | "picture", 1212 | "pictured", 1213 | "pie", 1214 | "piece", 1215 | "pig", 1216 | "pile", 1217 | "pilot", 1218 | "pine", 1219 | "pink", 1220 | "pipe", 1221 | "pitch", 1222 | "place", 1223 | "plain", 1224 | "plan", 1225 | "plane", 1226 | "planet", 1227 | "planned", 1228 | "planning", 1229 | "plant", 1230 | "plastic", 1231 | "plate", 1232 | "plates", 1233 | "play", 1234 | "pleasant", 1235 | "please", 1236 | "pleasure", 1237 | "plenty", 1238 | "plural", 1239 | "plus", 1240 | "pocket", 1241 | "poem", 1242 | "poet", 1243 | "poetry", 1244 | "point", 1245 | "pole", 1246 | "police", 1247 | "policeman", 1248 | "political", 1249 | "pond", 1250 | "pony", 1251 | "pool", 1252 | "poor", 1253 | "popular", 1254 | "population", 1255 | "porch", 1256 | "port", 1257 | "position", 1258 | "positive", 1259 | "possible", 1260 | "possibly", 1261 | "post", 1262 | "pot", 1263 | "potatoes", 1264 | "pound", 1265 | "pour", 1266 | "powder", 1267 | "power", 1268 | "powerful", 1269 | "practical", 1270 | "practice", 1271 | "prepare", 1272 | "present", 1273 | "president", 1274 | "press", 1275 | "pressure", 1276 | "pretty", 1277 | "prevent", 1278 | "previous", 1279 | "price", 1280 | "pride", 1281 | "primitive", 1282 | "principal", 1283 | "principle", 1284 | "printed", 1285 | "private", 1286 | "prize", 1287 | "probably", 1288 | "problem", 1289 | "process", 1290 | "produce", 1291 | "product", 1292 | "production", 1293 | "program", 1294 | "progress", 1295 | "promised", 1296 | "proper", 1297 | "properly", 1298 | "property", 1299 | "protection", 1300 | "proud", 1301 | "prove", 1302 | "provide", 1303 | "public", 1304 | "pull", 1305 | "pupil", 1306 | "pure", 1307 | "purple", 1308 | "purpose", 1309 | "push", 1310 | "put", 1311 | "putting", 1312 | "quarter", 1313 | "queen", 1314 | "question", 1315 | "quick", 1316 | "quickly", 1317 | "quiet", 1318 | "quietly", 1319 | "quite", 1320 | "rabbit", 1321 | "race", 1322 | "radio", 1323 | "railroad", 1324 | "rain", 1325 | "raise", 1326 | "ran", 1327 | "ranch", 1328 | "range", 1329 | "rapidly", 1330 | "rate", 1331 | "rather", 1332 | "raw", 1333 | "rays", 1334 | "reach", 1335 | "read", 1336 | "reader", 1337 | "ready", 1338 | "real", 1339 | "realize", 1340 | "rear", 1341 | "reason", 1342 | "recall", 1343 | "receive", 1344 | "recent", 1345 | "recently", 1346 | "recognize", 1347 | "record", 1348 | "red", 1349 | "refer", 1350 | "refused", 1351 | "region", 1352 | "regular", 1353 | "related", 1354 | "relationship", 1355 | "religious", 1356 | "remain", 1357 | "remarkable", 1358 | "remember", 1359 | "remove", 1360 | "repeat", 1361 | "replace", 1362 | "replied", 1363 | "report", 1364 | "represent", 1365 | "require", 1366 | "research", 1367 | "respect", 1368 | "rest", 1369 | "result", 1370 | "return", 1371 | "review", 1372 | "rhyme", 1373 | "rhythm", 1374 | "rice", 1375 | "rich", 1376 | "ride", 1377 | "riding", 1378 | "right", 1379 | "ring", 1380 | "rise", 1381 | "rising", 1382 | "river", 1383 | "road", 1384 | "roar", 1385 | "rock", 1386 | "rocket", 1387 | "rocky", 1388 | "rod", 1389 | "roll", 1390 | "roof", 1391 | "room", 1392 | "root", 1393 | "rope", 1394 | "rose", 1395 | "rough", 1396 | "round", 1397 | "route", 1398 | "row", 1399 | "rubbed", 1400 | "rubber", 1401 | "rule", 1402 | "ruler", 1403 | "run", 1404 | "running", 1405 | "rush", 1406 | "sad", 1407 | "saddle", 1408 | "safe", 1409 | "safety", 1410 | "said", 1411 | "sail", 1412 | "sale", 1413 | "salmon", 1414 | "salt", 1415 | "same", 1416 | "sand", 1417 | "sang", 1418 | "sat", 1419 | "satellites", 1420 | "satisfied", 1421 | "save", 1422 | "saved", 1423 | "saw", 1424 | "say", 1425 | "scale", 1426 | "scared", 1427 | "scene", 1428 | "school", 1429 | "science", 1430 | "scientific", 1431 | "scientist", 1432 | "score", 1433 | "screen", 1434 | "sea", 1435 | "search", 1436 | "season", 1437 | "seat", 1438 | "second", 1439 | "secret", 1440 | "section", 1441 | "see", 1442 | "seed", 1443 | "seeing", 1444 | "seems", 1445 | "seen", 1446 | "seldom", 1447 | "select", 1448 | "selection", 1449 | "sell", 1450 | "send", 1451 | "sense", 1452 | "sent", 1453 | "sentence", 1454 | "separate", 1455 | "series", 1456 | "serious", 1457 | "serve", 1458 | "service", 1459 | "sets", 1460 | "setting", 1461 | "settle", 1462 | "settlers", 1463 | "seven", 1464 | "several", 1465 | "shade", 1466 | "shadow", 1467 | "shake", 1468 | "shaking", 1469 | "shall", 1470 | "shallow", 1471 | "shape", 1472 | "share", 1473 | "sharp", 1474 | "she", 1475 | "sheep", 1476 | "sheet", 1477 | "shelf", 1478 | "shells", 1479 | "shelter", 1480 | "shine", 1481 | "shinning", 1482 | "ship", 1483 | "shirt", 1484 | "shoe", 1485 | "shoot", 1486 | "shop", 1487 | "shore", 1488 | "short", 1489 | "shorter", 1490 | "shot", 1491 | "should", 1492 | "shoulder", 1493 | "shout", 1494 | "show", 1495 | "shown", 1496 | "shut", 1497 | "sick", 1498 | "sides", 1499 | "sight", 1500 | "sign", 1501 | "signal", 1502 | "silence", 1503 | "silent", 1504 | "silk", 1505 | "silly", 1506 | "silver", 1507 | "similar", 1508 | "simple", 1509 | "simplest", 1510 | "simply", 1511 | "since", 1512 | "sing", 1513 | "single", 1514 | "sink", 1515 | "sister", 1516 | "sit", 1517 | "sitting", 1518 | "situation", 1519 | "six", 1520 | "size", 1521 | "skill", 1522 | "skin", 1523 | "sky", 1524 | "slabs", 1525 | "slave", 1526 | "sleep", 1527 | "slept", 1528 | "slide", 1529 | "slight", 1530 | "slightly", 1531 | "slip", 1532 | "slipped", 1533 | "slope", 1534 | "slow", 1535 | "slowly", 1536 | "small", 1537 | "smaller", 1538 | "smallest", 1539 | "smell", 1540 | "smile", 1541 | "smoke", 1542 | "smooth", 1543 | "snake", 1544 | "snow", 1545 | "so", 1546 | "soap", 1547 | "social", 1548 | "society", 1549 | "soft", 1550 | "softly", 1551 | "soil", 1552 | "solar", 1553 | "sold", 1554 | "soldier", 1555 | "solid", 1556 | "solution", 1557 | "solve", 1558 | "some", 1559 | "somebody", 1560 | "somehow", 1561 | "someone", 1562 | "something", 1563 | "sometime", 1564 | "somewhere", 1565 | "son", 1566 | "song", 1567 | "soon", 1568 | "sort", 1569 | "sound", 1570 | "source", 1571 | "south", 1572 | "southern", 1573 | "space", 1574 | "speak", 1575 | "special", 1576 | "species", 1577 | "specific", 1578 | "speech", 1579 | "speed", 1580 | "spell", 1581 | "spend", 1582 | "spent", 1583 | "spider", 1584 | "spin", 1585 | "spirit", 1586 | "spite", 1587 | "split", 1588 | "spoken", 1589 | "sport", 1590 | "spread", 1591 | "spring", 1592 | "square", 1593 | "stage", 1594 | "stairs", 1595 | "stand", 1596 | "standard", 1597 | "star", 1598 | "stared", 1599 | "start", 1600 | "state", 1601 | "statement", 1602 | "station", 1603 | "stay", 1604 | "steady", 1605 | "steam", 1606 | "steel", 1607 | "steep", 1608 | "stems", 1609 | "step", 1610 | "stepped", 1611 | "stick", 1612 | "stiff", 1613 | "still", 1614 | "stock", 1615 | "stomach", 1616 | "stone", 1617 | "stood", 1618 | "stop", 1619 | "stopped", 1620 | "store", 1621 | "storm", 1622 | "story", 1623 | "stove", 1624 | "straight", 1625 | "strange", 1626 | "stranger", 1627 | "straw", 1628 | "stream", 1629 | "street", 1630 | "strength", 1631 | "stretch", 1632 | "strike", 1633 | "string", 1634 | "strip", 1635 | "strong", 1636 | "stronger", 1637 | "struck", 1638 | "structure", 1639 | "struggle", 1640 | "stuck", 1641 | "student", 1642 | "studied", 1643 | "studying", 1644 | "subject", 1645 | "substance", 1646 | "success", 1647 | "successful", 1648 | "such", 1649 | "sudden", 1650 | "suddenly", 1651 | "sugar", 1652 | "suggest", 1653 | "suit", 1654 | "sum", 1655 | "summer", 1656 | "sun", 1657 | "sunlight", 1658 | "supper", 1659 | "supply", 1660 | "support", 1661 | "suppose", 1662 | "sure", 1663 | "surface", 1664 | "surprise", 1665 | "surrounded", 1666 | "swam", 1667 | "sweet", 1668 | "swept", 1669 | "swim", 1670 | "swimming", 1671 | "swing", 1672 | "swung", 1673 | "syllable", 1674 | "symbol", 1675 | "system", 1676 | "table", 1677 | "tail", 1678 | "take", 1679 | "taken", 1680 | "tales", 1681 | "talk", 1682 | "tall", 1683 | "tank", 1684 | "tape", 1685 | "task", 1686 | "taste", 1687 | "taught", 1688 | "tax", 1689 | "tea", 1690 | "teach", 1691 | "teacher", 1692 | "team", 1693 | "tears", 1694 | "teeth", 1695 | "telephone", 1696 | "television", 1697 | "tell", 1698 | "temperature", 1699 | "ten", 1700 | "tent", 1701 | "term", 1702 | "terrible", 1703 | "test", 1704 | "than", 1705 | "thank", 1706 | "that", 1707 | "thee", 1708 | "them", 1709 | "themselves", 1710 | "then", 1711 | "theory", 1712 | "there", 1713 | "therefore", 1714 | "these", 1715 | "they", 1716 | "thick", 1717 | "thin", 1718 | "thing", 1719 | "think", 1720 | "third", 1721 | "thirty", 1722 | "this", 1723 | "those", 1724 | "thou", 1725 | "though", 1726 | "thought", 1727 | "thousand", 1728 | "thread", 1729 | "three", 1730 | "threw", 1731 | "throat", 1732 | "through", 1733 | "throughout", 1734 | "throw", 1735 | "thrown", 1736 | "thumb", 1737 | "thus", 1738 | "thy", 1739 | "tide", 1740 | "tie", 1741 | "tight", 1742 | "tightly", 1743 | "till", 1744 | "time", 1745 | "tin", 1746 | "tiny", 1747 | "tip", 1748 | "tired", 1749 | "title", 1750 | "to", 1751 | "tobacco", 1752 | "today", 1753 | "together", 1754 | "told", 1755 | "tomorrow", 1756 | "tone", 1757 | "tongue", 1758 | "tonight", 1759 | "too", 1760 | "took", 1761 | "tool", 1762 | "top", 1763 | "topic", 1764 | "torn", 1765 | "total", 1766 | "touch", 1767 | "toward", 1768 | "tower", 1769 | "town", 1770 | "toy", 1771 | "trace", 1772 | "track", 1773 | "trade", 1774 | "traffic", 1775 | "trail", 1776 | "train", 1777 | "transportation", 1778 | "trap", 1779 | "travel", 1780 | "treated", 1781 | "tree", 1782 | "triangle", 1783 | "tribe", 1784 | "trick", 1785 | "tried", 1786 | "trip", 1787 | "troops", 1788 | "tropical", 1789 | "trouble", 1790 | "truck", 1791 | "trunk", 1792 | "truth", 1793 | "try", 1794 | "tube", 1795 | "tune", 1796 | "turn", 1797 | "twelve", 1798 | "twenty", 1799 | "twice", 1800 | "two", 1801 | "type", 1802 | "typical", 1803 | "uncle", 1804 | "under", 1805 | "underline", 1806 | "understanding", 1807 | "unhappy", 1808 | "union", 1809 | "unit", 1810 | "universe", 1811 | "unknown", 1812 | "unless", 1813 | "until", 1814 | "unusual", 1815 | "up", 1816 | "upon", 1817 | "upper", 1818 | "upward", 1819 | "us", 1820 | "use", 1821 | "useful", 1822 | "using", 1823 | "usual", 1824 | "usually", 1825 | "valley", 1826 | "valuable", 1827 | "value", 1828 | "vapor", 1829 | "variety", 1830 | "various", 1831 | "vast", 1832 | "vegetable", 1833 | "verb", 1834 | "vertical", 1835 | "very", 1836 | "vessels", 1837 | "victory", 1838 | "view", 1839 | "village", 1840 | "visit", 1841 | "visitor", 1842 | "voice", 1843 | "volume", 1844 | "vote", 1845 | "vowel", 1846 | "voyage", 1847 | "wagon", 1848 | "wait", 1849 | "walk", 1850 | "wall", 1851 | "want", 1852 | "war", 1853 | "warm", 1854 | "warn", 1855 | "was", 1856 | "wash", 1857 | "waste", 1858 | "watch", 1859 | "water", 1860 | "wave", 1861 | "way", 1862 | "we", 1863 | "weak", 1864 | "wealth", 1865 | "wear", 1866 | "weather", 1867 | "week", 1868 | "weigh", 1869 | "weight", 1870 | "welcome", 1871 | "well", 1872 | "went", 1873 | "were", 1874 | "west", 1875 | "western", 1876 | "wet", 1877 | "whale", 1878 | "what", 1879 | "whatever", 1880 | "wheat", 1881 | "wheel", 1882 | "when", 1883 | "whenever", 1884 | "where", 1885 | "wherever", 1886 | "whether", 1887 | "which", 1888 | "while", 1889 | "whispered", 1890 | "whistle", 1891 | "white", 1892 | "who", 1893 | "whole", 1894 | "whom", 1895 | "whose", 1896 | "why", 1897 | "wide", 1898 | "widely", 1899 | "wife", 1900 | "wild", 1901 | "will", 1902 | "willing", 1903 | "win", 1904 | "wind", 1905 | "window", 1906 | "wing", 1907 | "winter", 1908 | "wire", 1909 | "wise", 1910 | "wish", 1911 | "with", 1912 | "within", 1913 | "without", 1914 | "wolf", 1915 | "women", 1916 | "won", 1917 | "wonder", 1918 | "wonderful", 1919 | "wood", 1920 | "wooden", 1921 | "wool", 1922 | "word", 1923 | "wore", 1924 | "work", 1925 | "worker", 1926 | "world", 1927 | "worried", 1928 | "worry", 1929 | "worse", 1930 | "worth", 1931 | "would", 1932 | "wrapped", 1933 | "write", 1934 | "writer", 1935 | "writing", 1936 | "written", 1937 | "wrong", 1938 | "wrote", 1939 | "yard", 1940 | "year", 1941 | "yellow", 1942 | "yes", 1943 | "yesterday", 1944 | "yet", 1945 | "you", 1946 | "young", 1947 | "younger", 1948 | "your", 1949 | "yourself", 1950 | "youth", 1951 | "zero", 1952 | "zebra", 1953 | "zipper", 1954 | "zoo", 1955 | "zulu", 1956 | ]; 1957 | 1958 | export function generateRandomWords(mt: MersenneTwister, length = 4): string[] { 1959 | const words: string[] = []; 1960 | for (let i = 0; i < length + 0; ++i) { 1961 | const min = i * (wordList.length / length), 1962 | max = (i + 1) * (wordList.length / length); 1963 | const rand = (mt.random() * (max - min) + min) | 0, 1964 | word = [...wordList[rand]]; 1965 | word.unshift(word.shift()!.toUpperCase()); 1966 | words.push(word.join("")); 1967 | } 1968 | return words; 1969 | } 1970 | --------------------------------------------------------------------------------